{"posts":[{"title":"ADB工具简单学习（一）","content":"ADB工具 1. 获取设备序列号 adb get-serialno 2. 获取设备电量 adb shell dumpsys battery adb shell dumpsys battery set usb 1 设置为USB充电状态 Current Battery Service state: AC powered: false USB powered: false Wireless powered: false Max charging current: 0 Max charging voltage: 0 Charge counter: 3433133 status: 3 health: 2 present: true level: 90 scale: 100 voltage: 4232 temperature: 337 technology: Li-ion 3. 获取设备内存 adb shell cat /proc/meminfo adb shell procrank adb shell dumpsys meminfo Total RAM: 5,981,832K (status normal) Free RAM: 4,121,672K ( 123,068K cached pss + 3,998,604K available) Used RAM: 1,610,051K (1,372,495K used pss + 237,556K kernel) Lost RAM: 44,661K ZRAM: 20K physical used for 224K in swap (1,048,572K total swap) Tuning: 256 (large 512), oom 322,560K, restore limit 107,520K (high-end-gfx) 4. 获取设备容量 adb shell df /data/media adb shell df /data 5. 获取设备列表 adb devices 6. 获取设备状态 adb get-state device：设备正常连接 offline：连接异常，设备无响应 unknown：没有连接的设备 7. 安装应用 adb install packageName 执行结果： Success：安装成功 INSTALL_FAILED_ALREADY_EXISTS：安装失败 INSTALL_FAILED_SIGNATURE_ERROR：签名不一致 INSTALL_FAILED_INSUFFICIENT_STORAGE：存储空间不足 adb install -r packageName：重新安装 8. 卸载应用 adb uninstall 9. 文件互传 将本地文件传到设备上 adb push 本地文件全路径 /sdcard/ 将设备上的文件移动到本地 adb pull /sdcard/demo.txt 本地路径 10. 获取设备上所有应用信息 adb shell pm list package -s：列出所有系统应用包名 -f：列出所有应用包名，apk名及存放位置 11. 设备重启 adb reboot 12. 查看Android系统版本 adb shell getprop ro.build.version.release 13. 获取CPU信息 adb shell cat /proc/cpuinfo 14. 设备关机 adb shell reboot -p 15. 屏幕点击事件 adb shell input tap x轴 y轴 16. 屏幕滑动事件 adb shell input swipe x轴start y轴start x轴end y轴end 17. 屏幕按键事件 adb shell input keyevent 按键事件 18. 获取设备型号 adb -d shell getprop ro.product.model 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/gipeLHNM2/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/gipeLHNM2/"},{"title":"CRON表达式学习","content":"CRON表达式 为程序中的定时任务做执行计划和执行编排的一种表达式 一、基本格式 {秒数}{分钟数}{小时数}{日期}{月份}{星期}{年份[可空]} 1. 详解 1.1 {秒数} 范围：0-59 限制：不许为空 注意：值不合法，调度器将会抛出 ScheduleException 异常 *：表示每隔1秒钟触发定时任务 ,：表示在指定秒数触发定时任务 示例：0,15,30,45 表示在0秒，15秒，30秒，45秒时触发定时任务 -：表示在指定秒数范围内触发定时任务，每隔1秒钟触发一次定时任务 示例：20-30 表示在20秒到30秒的范围内，每隔1秒钟触发定时任务 /：表示每隔指定秒数步长就触发定时任务 示例：0/20 表示从0秒钟开始，每隔20秒钟触发一次定时任务 1.2 {分钟数} 范围：0-59 限制：不许为空 注意：值不合法，调度器将会抛出 ScheduleException 异常 *：表示每隔1分钟触发定时任务 ,：表示在指定分钟数触发定时任务 -：表示在指定分钟数范围内触发定时任务，每隔1分钟触发一次定时任务 /：表示每隔指定分钟数步长就触发定时任务 1.3 {小时数} 范围：0-23 限制：不许为空 注意：值不合法，调度器将会抛出 ScheduleException 异常 *：表示每隔1小时触发定时任务 ,：表示在指定小时数触发定时任务 -：表示在指定小时数范围内触发定时任务，每隔1小时触发一次定时任务 /：表示每隔指定小时数步长就触发定时任务 1.4 {日期} 范围：1-31 限制：不许为空 注意：值不合法，调度器将会抛出 ScheduleException 异常 *：表示每隔1天触发定时任务 ?：与{星期}互斥，表示匹配该域任意值 ,：表示在指定日期触发定时任务 -：表示在指定日期范围内触发定时任务，每隔1天触发一次定时任务 /：表示每隔指定天数步长就触发定时任务 L：表示当月的最后一天触发定时任务 W：表示离当天最近的一个工作日触发定时任务 LW：表示当月的最后一个工作日触发定时任务 C：表示根据日历触发定时任务，不常用 1.5 {月份} 范围：1-12 限制：不许为空 注意：值不合法，调度器将会抛出 ScheduleException 异常 *：表示每隔1个月触发定时任务 ,：表示在指定月份触发定时任务 -：表示在指定月份范围内触发定时任务，每隔1个月触发一次定时任务 /：表示每隔指定月份步长就触发定时任务 1.6 {星期} 范围：1-7 限制：不许为空 注意： 1为星期日，2为星期一，3为星期二，4为星期三，5为星期四，6为星期五 值不合法，调度器将会抛出 ScheduleException 异常 *：表示每隔1个星期触发定时任务 ?：与{日期}互斥，表示匹配该域任意值 ,：表示在指定星期触发定时任务 -：表示在指定星期范围内触发定时任务，每隔1天触发一次定时任务 /：表示每隔指定的天数步长就触发定时任务 L：表示当前星期的最后一天触发定时任务，即星期六 #：用来表示指定周数，#前面表示星期，#后面表示本月第几周 示例：2#2 表示本月第二周的星期一 C：根据日历触发，使用较少 1.7 {年份} 范围：1970-2099 限制：允许为空 注意：值不合法，调度器将会抛出 ScheduleException 异常 *：表示每隔1年触发定时任务 ,：表示在指定年份触发定时任务 -：表示在指定年份范围内触发定时任务，每隔1年触发一次定时任务 /：表示每隔指定的年份步长就触发定时任务 2. 经典案例 30 * * * * ? 每半分钟触发任务 30 10 * * * ? 每小时的10分30秒触发任务 30 10 1 * * ? 每天1点10分30秒触发任务 30 10 1 20 * ? 每月20号1点10分30秒触发任务 30 10 1 20 10 ? * 每年10月20号1点10分30秒触发任务 30 10 1 20 10 ? 2011 2011年10月20号1点10分30秒触发任务 30 10 1 ? 10 * 2011 2011年10月每天1点10分30秒触发任务 30 10 1 ? 10 SUN 2011 2011年10月每周日1点10分30秒触发任务 15,30,45 * * * * ? 每15秒，30秒，45秒时触发任务 15-45 * * * * ? 15到45秒内，每秒都触发任务 15/5 * * * * ? 每分钟的每15秒开始触发，每隔5秒触发一次 15-30/5 * * * * ? 每分钟的15秒到30秒之间开始触发，每隔5秒触发一次 0 0/3 * * * ? 每小时的第0分0秒开始，每三分钟触发一次 0 15 10 ? * MON-FRI 星期一到星期五的10点15分0秒触发任务 0 15 10 L * ? 每个月最后一天的10点15分0秒触发任务 0 15 10 LW * ? 每个月最后一个工作日的10点15分0秒触发任务 0 15 10 ? * 5L 每个月最后一个星期四的10点15分0秒触发任务 0 15 10 ? * 5#3 每个月第三周的星期四的10点15分0秒触发任务 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/RGEfzFph6/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/RGEfzFph6/"},{"title":"SpringSecurity学习","content":"SpringSecurity 一、初识SpringSecurity 1. 基本概念 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean，充分利用了Spring IoC，DI（控制反转Inversion of Control ,DI：Dependency Injection 依赖注入）和AOP（面向切面编程）功能，为应用系统提供声明式的安全访问控制功能，减少了为企业系统安全控制编写大量重复代码的工作。 常用安全框架 Spring Security Apache Shiro 2. 过滤器链 3. 认证流程图 UsernamePasswordAuthenticationFilter 负责处理我们在登录页面填写了用户名和密码之后的登录请求 ExceptionTranslationFilter 处理过滤器链中跑出的任何 AccessDeniedException 和 AuthenticationException FilterSecurityInterceptor 负责权限校验的过滤器 Authentication ：接口 他的实现类表示当前访问系统的用户，封装与用户相关的所有信息 AuthenticationManager：接口 定义了认证Authentication对象的方法 UserDetailsService：接口 加载用户特定数据的核心接口，里面定义了一个loadUserByUsername()的方法 loadUserByUsername() 根据用户名获取用户信息 如果该用户存在，将查询到的用户信息封装到UserDetails对象返回 UserDetails：接口 提供用户的核心信息，通过loadUserByUsername()接口返回的用户对象中的信息，将信息设置到Authentication对象中 Authentication：对象 拥有权限列表信息的登录用户实体信息封装对象 4. 快速入门 4.1 引入依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.projectlombok&lt;/groupId&gt; &lt;artifactId&gt;lombok&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.baomidou&lt;/groupId&gt; &lt;artifactId&gt;mybatis-plus-boot-starter&lt;/artifactId&gt; &lt;version&gt;${mybatis-plus.version}&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;mysql&lt;/groupId&gt; &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.alibaba&lt;/groupId&gt; &lt;artifactId&gt;fastjson&lt;/artifactId&gt; &lt;version&gt;1.2.71&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt; &lt;artifactId&gt;jjwt&lt;/artifactId&gt; &lt;version&gt;0.9.0&lt;/version&gt; &lt;/dependency&gt; 1.2 创建启动类 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } 1.3 简单yml配置 server: port: 80 servlet: context-path: / spring: application: name: demo datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql//localhost:3306/security username: root password: 996748 1.4 书写简单Controller @RestController public class TestController{ @GetMapping(&quot;/test&quot;) public String test(){ return &quot;test&quot;; } } 1.5 访问项目地址 localhost/test 页面被重定向到SpringSecurity的默认登录页 默认账号 用户名：user 密码：控制台打印密码 登录成功后，可以查看到我们书写的简易controller返回信息test 二、SecurityConfig配置类 实际开发中我们不会把明文密码存入数据库中 默认使用的PassworEncoder要求数据库中的密码格式为{id}password，他会根据id去判断密码的加密方式，如果不写会出错，但是通常我们是不会采用这种方式的，所以我们需要替换默认的PasswordEncoder 我们只需要把要使用的BCryptPasswordEncoder注入Spring容器，SpringSecurity就会使用该编码器进行密码校验，我们可以定义一个SecurityConfig的配置类并继承WebSecurityConfigurerAdapter @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { //编码器BCryptPasswordEncoder注入容器 @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } //认证管理器注入容器 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { //表单提交 http.formLogin() //添加表单参数映射，SpringSecurity默认只接受username和password参数，如果我们需要不同名称的参数，需要在此处指定名称映射 .usernameParameter(&quot;username&quot;) .passwordParameter(&quot;password&quot;) //指定登录请求服务地址，与表单提交地址一致 .loginProcessingUrl(&quot;/login&quot;) //自定义登录页面 .loginPage(&quot;/login.html&quot;) //自定义成功页面，只支持POST方式，因而在前后端分离项目中无法使用 .successForwardUrl(&quot;/toIndex&quot;) //自定义失败页面，只支持POST方式，因而在前后端分离项目中无法使用 .failureForwardUrl(&quot;/toError&quot;); //授权认证 http.authorizeRequests() //设置过滤器规则，增加页面与访问权限信息 .antMatchers(&quot;/login.html&quot;,&quot;/error.html&quot;).permitAll() //混合项目中放行所有静态资源 .antMatchers(&quot;/js/**&quot;,&quot;/css/**&quot;,&quot;/images/**&quot;).permitAll() .antMatchers(&quot;/admin.html&quot;).hasRole() .anyRequest().authenticated(); //关闭csrf防护 http.csrf().disable(); } } 1. csrf 2. formLogin 表单参数映射 usernameParameter()：设置表单提交的username字段名称映射 passwordParameter()：设置表单提交的password字段名称映射 自定义请求与页面 loginProcessingUrl()：指定自定义的登录请求地址 loginPage()：指定自定义的登录页 successForwardUrl()：指定自定义的登录成功后欲跳转的页面 failureForwardUrl()：指定自定义的登录失败后欲跳转的页面 3. authorizeRequests 访问权限控制 permitAll()：无论是否登录均开放访问 denyAll()：无论是否登录均不开放访问 anonymous()：在未登录状态下，允许所有人访问 authenticated()：在登录状态下，通过认证的可以访问 fullyAuthenticated()：必须通过用户名密码直接的登录的用户才可以访问，勾选了记住我登录的用户无权访问 rememberMe()：通过勾选了记住我登录的用户才可以访问 访问权限判断 hasAuthority([权限名称]) hasAnyAuthority([权限列表，逗号分隔]) 访问角色判断 hasRole([角色名称]) hasAnyRole([角色列表，逗号分隔]) 访问IP判断 hasIpAddress([IP地址]) 访问权限与访问角色是严格区分大小写的，不同的字母会导致不同角色的产生 3.1 antMatchers 请求匹配器：可以为指定请求设置指定的访问权限 3.2 regexMatchers 正则匹配器：使用正则表达式对指定格式的请求放行 3.3 mvcMatchers 在application.yml配置文件中配置过如下信息的，可以使用mvcMatchers指定servletPath(&quot;/demo&quot;) spring: mvc: servlet: path: /demo http.authorizeRequests() .mvcMatchers(&quot;/login&quot;).servletPath(&quot;/demo&quot;).permitAll(); 3.4 anyRequests 除了其他匹配器匹配的请求之外的所有请求 authenticated()：登录认证后可访问 无论是antMatchers还是regexMatchers，他们都有两个参数的方法，可以使用这些方法来限定请求方式 antMatchers(HttpMethod method,String patterns) regexMatchers(HttpMethod method,String regexPatterns) 4. exceptionHandling accessDeniedHandler()：设置访问拒绝的处理器 5. rememberMe 三、认证流程 SpringSecurity有对UserDetails默认的实现类User，用来作为请求以及返回的实体信息，我们可以通过自己实现UserDetails来创建我们自定义的请求返回实体信息，并且实现UserDetailsService来实现我们自定义的登录逻辑 1. 实现自定义的登录验证对象 1.1 自定义用户实体类 @Data @AllArgsConstructor @NoArgsConstructor public class UserInfo { private String username; private String password; } 1.2 实现UserDetails接口创建自定义的验证对象 @Data public class LoginUser implements UserDetails { private UserInfo userInfo; private List&lt;GrantedAuthority&gt; authorities; public LoginUser(UserInfo userInfo, List&lt;GrantedAuthority&gt; authorities) { this.userInfo = userInfo; this.authorities = authorities; } @Override public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() { return null; } @Override public String getPassword() { return userInfo.getPassword(); } @Override public String getUsername() { return userInfo.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } 接下来我们需要自定义登录接口，然后让SpringSecurity对这个接口放行，用户在未登录前当然是没有权限的，所以我们应该将登录接口开放为所有人都可以访问 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证，所以需要在SecurityConfig中配置AuthenticationManager注入Spring容器 认证成功后要生成一个jwt（json-web-token），放入响应体返回，为了让用户下回请求能通过jwt识别出具体是哪个用户，我们需要把用户信息存入redis，以用户id作为作为key 2. 实现自定义的登录逻辑 @Service public class UserDetailServiceImpl implements UserDetailsService { @Resource private UserInfoMapper userInfoMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper&lt;UserInfo&gt; userInfoQueryWrapper = new QueryWrapper&lt;&gt;(); userInfoQueryWrapper.eq(&quot;username&quot;,username); UserInfo userInfo = userInfoMapper.selectOne(userInfoQueryWrapper); if(userInfo==null){ throw new UsernameNotFoundException(&quot;用户名或密码错误&quot;); } return new LoginUser(userInfo); } } 3. SecurityConfig @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private MyAccessDeniedHandler myAccessDeniedHandler; @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { //关闭防护 http.csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() //对于登录注册接口允许匿名访问 .antMatchers(&quot;/user/login&quot;,&quot;/user/register&quot;).anonymous() //除去以上所有配置过的请求，其余请求都需要鉴权认证 .anyRequest().authenticated() .and() .formLogin() .usernameParameter(&quot;username&quot;) .passwordParameter(&quot;password&quot;) .loginProcessingUrl(&quot;/login&quot;) .loginPage(&quot;/login.html&quot;) .successHandler(new MyAuthenticationSuccessHandler(&quot;/admin&quot;)) .failureHandler(new MyAuthenticationFailureHandler(&quot;/error&quot;)); .and() .exceptionHandling() .accessDeniedHandler(myAccessDeniedHandler); } } 4. 实现自定义的登录服务对用户进行认证 @Service public class LoginServiceImpl implements LoginService { @Resource private RedisCache redisCache; @Resource private AuthenticationManager authenticationManager; @Override public Response login(UserInfo userInfo){ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userInfo.getUsername(), userInfo.getPassword()); Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken); if(authenticate==null){ throw new RuntimeException(&quot;登录失败&quot;); }else { Object principal = authenticate.getPrincipal(); if ((principal instanceof LoginUser)){ LoginUser loginUser = (LoginUser) principal; String id = loginUser.getUserInfo().getId().toString(); String username = loginUser.getUserInfo().getUsername(); String jwt = JWTUtils.getJWT(id, username); Response response = new Response(); response.code(ResponseCode.SUCCESS_CODE.getCode()) .message(ResponseCode.SUCCESS_CODE.getMessage()) .data(null) .count(1) .token(jwt); redisCache.setCacheObject(&quot;login:&quot; + id,loginUser); return response; } } return null; } } 5. 编写Controller @RestController @RequestMapping(&quot;/user&quot;) public class LoginController { @Resource private LoginService loginService; @PostMapping(&quot;/login&quot;) public Response login(@RequestBody UserInfo userInfo){ return loginService.login(userInfo); } @PostMapping(&quot;/logout&quot;) public Response logout(){ return loginService.logout(); } } 6. 实现Token认证过滤器 @Component public class JWTAuthenticationFilter extends OncePerRequestFilter { @Resource private RedisCache redisCache; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String token = httpServletRequest.getHeader(&quot;token&quot;); if(StringUtils.isEmpty(token)){ filterChain.doFilter(httpServletRequest,httpServletResponse); return; }else { if(JWTUtils.isValid(token)){ String uid = JWTUtils.getClaimInfoForString(httpServletRequest, &quot;uid&quot;); Object cacheObject = redisCache.getCacheObject(&quot;login:&quot; + uid); if (cacheObject instanceof LoginUser){ LoginUser loginUser = (LoginUser) cacheObject; if (loginUser==null){ return; }else { //TODO 未构建权限列表 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); filterChain.doFilter(httpServletRequest,httpServletResponse); } } } } return; } } 7. 更新SecurityConfig配置 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private MyAccessDeniedHandler myAccessDeniedHandler; @Resource private JWTAuthenticationFilter jwtAuthenticationFilter; @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { //关闭防护 http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(&quot;/user/login&quot;,&quot;/user/register&quot;).anonymous() .anyRequest().authenticated() .and() .formLogin() .usernameParameter(&quot;username&quot;) .passwordParameter(&quot;password&quot;) .loginProcessingUrl(&quot;/login&quot;) .loginPage(&quot;/login.html&quot;) .successHandler(new MyAuthenticationSuccessHandler(&quot;/successUrl&quot;)) .failureHandler(new MyAuthenticationFailureHandler(&quot;/failureUrl&quot;)) .and() .exceptionHandling() .accessDeniedHandler(myAccessDeniedHandler) .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } } 8. 退出登录 @Override public Response logout() { UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if(principal instanceof LoginUser){ LoginUser loginUser = (LoginUser) principal; String uid = loginUser.getUserInfo().getId().toString(); redisCache.deleteObject(&quot;login:&quot; + uid); Response response = new Response(); response.code(ResponseCode.SUCCESS_CODE.getCode()) .message(&quot;注销成功&quot;) .count(0) .data(null) .token(&quot;&quot;); return response; } return null; } 四、前后端分离场景下登录认证跳转解决方案 SpringSecurity默认的认证成功跳转是一种混合项目的post请求，但是目前越来越多的前后端分离项目涌现或者说在前后端分离开发变成一种规范的当下，这种形式已经不再适用我们的开发要求了，所以我们需要自定义SpringSecurity认证成功重定向的处理器，我们通过实现AuthenticationSuccessHandler AuthenticationFailureHandler接口来自定义我们认证成功或失败后的重定向方式 1. 重写认证授权成功处理器 //实现AuthenticationSuccessHandler接口完成请求转发或重定向操作，对应登录成功后的操作 public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private String successUrl; public MyAuthenticationSuccessHandler(String successUrl) { this.successUrl = successUrl; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.sendRedirect(successUrl); } } 2. 重写认证授权失败处理器 //实现AuthenticationFailureHandler接口完成请求转发或重定向操作，对应登录失败后的操作 public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { private String failureUrl; public MyAuthenticationSuccessHandler(String failureUrl) { this.failureUrl = failureUrl; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.sendRedirect(failureUrl); } } 3. 配置自定义处理器 //使用我们实现的SuccessHandler和FailureHandler设置登录成功和失败后的请求重定向 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .usernameParameter(&quot;username&quot;) .passwordParameter(&quot;password&quot;) .loginProcessingUrl(&quot;/login&quot;) .loginPage(&quot;/login.html&quot;) //此处使用我们自己实现的重定向处理器，这样就可以支持前后端分离了 .successHandler(new MyAuthenticationSuccessHandler(&quot;/admin&quot;)) .failureHandler(new MyAuthenticationFailureHandler(&quot;/error&quot;)); http.authorizeRequests() .antMatchers(&quot;/login.html&quot;,&quot;/error.html&quot;).permitAll() .antMatchers(&quot;/admin.html&quot;).hasRole(&quot;ADMIN&quot;) .anyRequest().authenticated(); http.csrf().disable(); } } 五、授权流程 在SpringSecurity中，会默认使用FilterSecurityInterceptor进行权限校验，FilterSecurityInterceptor会从SecurityContextHolder中获取Authentication，然后获取其中的权限信息，判断当前用户是否拥有访问当起资源的权限 所以我们需要将当前登录用户的权限信息存入Authentication 然后将对应的资源设置好访问权限 如果满足角色条件，程序正常执行，如果不满足，抛出 org.springframework.security.access.AccessDeniedException 1. 授权实现 1.1 限制访问资源所需权限 @Secured 专门用于判断是否具有指定角色权限，可用在方法上或类上，与hasRole方法相反，参数要以ROLE_开头 使用方式 @SpringBootApplication //开启注解访问控制 @EnableGlobalMethodSecurity(SecuredEnabled = true) public class SpringSecurityDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringSecurityDemoApplication.class, args); } } @Secured(&quot;ROLE_ADMIN&quot;) @GetMapping(&quot;/admin&quot;) public String toAdmin(){ return &quot;redirect:admin.html&quot;; } 此时当我们访问到/admin时，就会检测用户是否登录成功，如果登录成功判断用户是否存在角色权限，如果存在访问成功，否则500 @PreAuthorize 和 @PostAuthorize @PreAuthorize 表示在访问方法或类之前先判断权限，注解参数和access方法参数格式相同，都为权限表达式 @PostAuthorize 表示在访问方法或类之后判断权限，很少用 使用方式 @SpringBootApplication //开启注解访问控制 @EnableGlobalMethodSecurity(prePostEnable = true) public class SpringSecurityDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringSecurityDemoApplication.class, args); } } @PreAuthorize(&quot;hasRole(ADMIN)&quot;)//PreAuthorize允许角色ROLE_开头 @GetMapping(&quot;/admin&quot;) public String toAdmin(){ return &quot;redirect:admin.html&quot;; } 1.2 封装权限信息 事实上我之前在UserDetailsServiceImpl中不仅需要查询出数据库中的用户信息，而且还要查询出对应的权限信息，一并封装到UserDetails中返回，我们自己实现了UserDetails为LoginUser @Data @NoArgsConstructor public class LoginUser implements UserDetails { private UserInfo userInfo; private List&lt;String&gt; roleList; @JSONField(serialize = false) private HashSet&lt;GrantedAuthority&gt; authorities; public LoginUser(UserInfo userInfo, List&lt;String&gt; roleList) { this.userInfo = userInfo; this.roleList = roleList; } public LoginUser(UserInfo userInfo) { this.userInfo = userInfo; } @Override public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() { if(authorities!=null &amp;&amp; authorities.size()!=0){ return authorities; } for (String role : roleList) { authorities.add(new SimpleGrantedAuthority(role)); } //authorities = roleList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); return authorities; } @Override public String getPassword() { return userInfo.getPassword(); } @Override public String getUsername() { return userInfo.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } @Service public class UserDetailServiceImpl implements UserDetailsService { @Resource private UserInfoMapper userInfoMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper&lt;UserInfo&gt; userInfoQueryWrapper = new QueryWrapper&lt;&gt;(); userInfoQueryWrapper.eq(&quot;username&quot;,username); UserInfo userInfo = userInfoMapper.selectOne(userInfoQueryWrapper); if(userInfo==null){ throw new UsernameNotFoundException(&quot;用户名或密码错误&quot;); } List&lt;String&gt; roleList = Arrays.asList(&quot;admin&quot;,&quot;user&quot;); return new LoginUser(userInfo,roleList); } } @Component public class JWTAuthenticationFilter extends OncePerRequestFilter { @Resource private RedisCache redisCache; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String token = httpServletRequest.getHeader(&quot;token&quot;); if(StringUtils.isEmpty(token)){ filterChain.doFilter(httpServletRequest,httpServletResponse); return; }else { if(JWTUtils.isValid(token)){ String uid = JWTUtils.getClaimInfoForString(httpServletRequest, &quot;uid&quot;); Object cacheObject = redisCache.getCacheObject(&quot;login:&quot; + uid); if (cacheObject instanceof LoginUser){ LoginUser loginUser = (LoginUser) cacheObject; if (loginUser==null){ return; }else { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); filterChain.doFilter(httpServletRequest,httpServletResponse); } } } } return; } } 2. 基于角色的权限控制（RBAC） 2.1 设计数据表 设计权限表 DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `menu_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '菜单名称即权限名称', `routing_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路由地址即服务地址', `component_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '组件路径', `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '菜单状态（0显示，1隐藏）', `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '功能状态（0正常，1停用）', `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限标识', `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '#' COMMENT '菜单图标', `create_by` bigint NULL DEFAULT NULL COMMENT '创建自', `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` bigint NULL DEFAULT NULL COMMENT '更新自', `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `del_flag` int NULL DEFAULT NULL COMMENT '是否删除（0未删除，1已删除）', `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '菜单（权限）表' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1; 设计角色表 DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `role_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名称', `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色权限字符串', `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '角色状态（0正常，1停用）', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '是否删除（0未删除，1已删除）', `create_by` bigint NULL DEFAULT NULL COMMENT '创建自', `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` bigint NULL DEFAULT NULL COMMENT '更新自', `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色表' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1; 设计角色-权限表 DROP TABLE IF EXISTS `sys_role_menu`; CREATE TABLE `sys_role_menu` ( `role_id` bigint NOT NULL COMMENT '角色ID', `menu_id` bigint NOT NULL DEFAULT 0 COMMENT '菜单ID', PRIMARY KEY (`role_id`, `menu_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1; 设计用户表 DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名', `nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称', `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码', `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '账号状态（0正常，1停用）', `email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱', `phone_number` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号', `gender` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户性别（0男，1女，2未知）', `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像地址', `user_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户类型（0管理员，1普通用户）', `create_by` bigint NULL DEFAULT NULL COMMENT '创建自', `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` bigint NULL DEFAULT NULL COMMENT '更新自', `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '是否删除（0未删除，1已删除）', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1; 设计用户-角色表 DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `user_id` bigint NOT NULL COMMENT '用户ID', `role_id` bigint NOT NULL COMMENT '角色ID', PRIMARY KEY (`user_id`, `role_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1; 2.2 从数据库中查询权限信息 六、access方法 access()方法中填写权限表达式，具体权限表达式如下表，另外支持自定义权限表达式 1. SpringSecurity规定一些常用表达式 表达式 说明 hasRole([role]) 用户拥有制定的角色时返回true （Spring security默认会带有ROLE_前缀） hasAnyRole([role1,role2]) 用户拥有任意一个制定的角色时返回true hasAuthority([authority]) 等同于hasRole,但不会带有ROLE_前缀 hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole permitAll 永远返回true denyAll 永远返回false authentication 当前登录用户的authentication对象 fullAuthenticated 当前用户既不是anonymous也不是rememberMe用户时返回true hasIpAddress('192.168.1.0/24') 请求发送的IP匹配时返回true 2. 自定义表达式 2.1 创建自定义接口 public interface MyAccessExpression { boolean hasPermission(HttpServletRequest request, Authentication authentication); } 2.2 实现自定义接口 @Component(&quot;myEX&quot;) public class MyAccessExpressionImpl implements MyAccessExpression { @Override public boolean hasPermission(String roleName) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if(principal instanceof LoginUser){ LoginUser loginUser = (LoginUser) principal; Collection&lt;? extends GrantedAuthority&gt; authorities = loginUser.getAuthorities(); return authorities.contains(roleName); } return false; } } 2.3 在SecurityConfig中配置自定义表达式 .anyRequest().access(&quot;@myEx.hasPermission('sys:file:delete')&quot;) 2.4 使用注解调用自定义表达式 @PreAuthorize(&quot;@myEX.hasPermission('sys:file:delete')&quot;) 七、自定义403处理方案 当用户请求认证失败或者是授权失败时，SpringSecurity会使用一些默认处理，会在前端展示一些状态信息，但这通常不是我们希望前端开发者以及用户看到的，所以我们应该给前端返回统一的response格式，让前端自行决定展示内容 认证失败的异常通常会由AuthenticationException调用AuthenticationEntryPoint对象的commence方法来处理 授权失败的异常通常会由AccessDeniedException调用AccessDeniedHandler对象的handle方法来处理 所以如果我们需要拦截这些默认的返回信息，就需要自定义AuthenticationEntryPoint和AccessDeniedHandler的实现类，重写它们对应的处理方法，即可返回我们需要的统一response格式 1. 自定义认证失败处理 1.1 实现AuthenticationEntryPoint接口 @Component public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpServletResponse.setContentType(&quot;application/json&quot;); httpServletResponse.setCharacterEncoding(&quot;UTF-8&quot;); PrintWriter writer = httpServletResponse.getWriter(); writer.println(JSON.toJSONString( new Response&lt;String&gt;().code(ResponseCode.UN_AUTHORIZATION_CODE.getCode()) .message(ResponseCode.UN_AUTHORIZATION_CODE.getMessage()) .count(0) .data(&quot;对不起，认证失败&quot;) )); writer.flush(); writer.close(); } } 1.2 配置自定义认证失败处理器 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private MyAccessDeniedHandler myAccessDeniedHandler; @Resource private JWTAuthenticationFilter jwtAuthenticationFilter; @Resource private AuthenticationEntryPointHandler authenticationEntryPointHandler; @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { //关闭防护 http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(&quot;/user/login&quot;,&quot;/user/register&quot;).anonymous() .anyRequest().authenticated() .anyRequest().access(&quot;@myAccessExpressionImpl.hasPermission(request,authentication)&quot;) .and() .formLogin() .usernameParameter(&quot;username&quot;) .passwordParameter(&quot;password&quot;) .loginProcessingUrl(&quot;/login&quot;) .loginPage(&quot;/login.html&quot;) .successHandler(new MyAuthenticationSuccessHandler(&quot;/successUrl&quot;)) .failureHandler(new MyAuthenticationFailureHandler(&quot;/failureUrl&quot;)) .and() .exceptionHandling() .accessDeniedHandler(myAccessDeniedHandler) .authenticationEntryPoint(authenticationEntryPointHandler) .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } } 2. 自定义授权失败处理 2.1 实现AccessDeniedHandler接口 @Component public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); httpServletResponse.setContentType(&quot;application/json&quot;); httpServletResponse.setCharacterEncoding(&quot;UTF-8&quot;); PrintWriter writer = httpServletResponse.getWriter(); writer.println(JSON.toJSONString( new Response&lt;String&gt;().code(ResponseCode.FORBIDDEN_CODE.getCode()) .message(ResponseCode.FORBIDDEN_CODE.getMessage()) .count(0) .data(&quot;对不起，您无权访问此页面&quot;) )); writer.flush(); writer.close(); } } 2.2 配置自定义授权失败处理器 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private MyAccessDeniedHandler myAccessDeniedHandler; @Resource private JWTAuthenticationFilter jwtAuthenticationFilter; @Resource private AuthenticationEntryPointHandler authenticationEntryPointHandler; @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { //关闭防护 http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(&quot;/user/login&quot;,&quot;/user/register&quot;).anonymous() .anyRequest().authenticated() .anyRequest().access(&quot;@myAccessExpressionImpl.hasPermission(request,authentication)&quot;) .and() .formLogin() .usernameParameter(&quot;username&quot;) .passwordParameter(&quot;password&quot;) .loginProcessingUrl(&quot;/login&quot;) .loginPage(&quot;/login.html&quot;) .successHandler(new MyAuthenticationSuccessHandler(&quot;/successUrl&quot;)) .failureHandler(new MyAuthenticationFailureHandler(&quot;/failureUrl&quot;)) .and() .exceptionHandling() .accessDeniedHandler(myAccessDeniedHandler) .authenticationEntryPoint(authenticationEntryPointHandler) .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } } 八、RememberMe功能实现 SpringSecurity中想要实现记住我功能，用户只需要在登录时添加value为remember-me的checkbox。取值为true，SpringSecurity会自动把用户信息存储到数据源中，以后就不用再使用用户名密码登录访问 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private UserDetailsService userDetailsService; @Resource private DataSource dataSource; @Resource private PersistentTokenRespository persistentTokenRespository; @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public PersistentTokenRespository getPersistentTokenRespository(){ JdbcTokenRespositoryImpl jsbcTokenRespository = new JdbcTokenRespositoryImpl(); jsbcTokenRespository.setDataSource(dataSource); //在数据库中创建表，首次创建后，无需再次创建 jsbcTokenRespository.setCreateTableOnStartup(true); return jsbcTokenRespository; } @Override protected void configure(HttpSecurity http) throws Exception { http.rememberMe() //token失效时间，单位秒 .tokenValiditySeconds(60*60*24*7) //绑定参数名称，默认为“remember-me” .rememberMeParameter(&quot;rememberMe&quot;) //自定义登录逻辑 .userDetailsService(userDetailsServiceImpl) //持久层对象 .tokenRespository(persistentTokenRespository); } } 九、退出登录处理器 1. 书写退出登录代码 @Override public Response logout() { UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if(principal instanceof LoginUser){ LoginUser loginUser = (LoginUser) principal; String uid = loginUser.getUserInfo().getId().toString(); redisCache.deleteObject(&quot;login:&quot; + uid); Response response = new Response(); response.code(ResponseCode.SUCCESS_CODE.getCode()) .message(&quot;注销成功&quot;) .count(0) .data(null) .token(&quot;&quot;); return response; } return null; } 2. 实现退出登录处理器 @Component public class MyLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { System.out.println(&quot;退出登录成功&quot;); } } 3. 配置自定义退出登录处理器 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private MyAccessDeniedHandler myAccessDeniedHandler; @Resource private JWTAuthenticationFilter jwtAuthenticationFilter; @Resource private AuthenticationEntryPointHandler authenticationEntryPointHandler; @Resource private MyLogoutSuccessHandler myLogoutSuccessHandler; @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { //关闭防护 http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(&quot;/user/login&quot;,&quot;/user/register&quot;).anonymous() .anyRequest().authenticated() .anyRequest().access(&quot;@myAccessExpressionImpl.hasPermission(request,authentication)&quot;) .and() .formLogin() .usernameParameter(&quot;username&quot;) .passwordParameter(&quot;password&quot;) .loginProcessingUrl(&quot;/login&quot;) .loginPage(&quot;/login.html&quot;) .successHandler(new MyAuthenticationSuccessHandler(&quot;/successUrl&quot;)) .failureHandler(new MyAuthenticationFailureHandler(&quot;/failureUrl&quot;)) .and() .exceptionHandling() .accessDeniedHandler(myAccessDeniedHandler) .authenticationEntryPoint(authenticationEntryPointHandler) .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .cors() .and() .logout() .logoutSuccessHandler(myLogoutSuccessHandler); } } 十、CSRF和CORS CSRF：跨站请求伪造，也被称为 OneClick Attack或者Session Rifing，通过伪造用户请求访问受信任站点的非法请求访问。 CORS：跨域资源共享，只要网络协议、IP地址、端口中的任何一个不相同就是跨域请求 1. SpringBoot开启跨域 @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(&quot;/**&quot;) .allowedOrigins(&quot;*&quot;) .allowCredentials(true) .allowedMethods(&quot;GET&quot;,&quot;POST&quot;,&quot;DELETE&quot;,&quot;PUT&quot;,&quot;OPTION&quot;) .allowedHeaders(&quot;*&quot;) .maxAge(3600); } } 2. SpringSecurity开启跨域 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private UserDetailsService userDetailsService; @Bean public PasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { //CSRF防护默认是开启的 //关闭CSRF防护 http.csrf().disable(); //允许跨域 http.cors(); } } 当CSRF防护开启时，前端提交登录请求参数必须携带_csrf参数并提交对应token值，后端会验证token是否为后端生成的令牌，确认通过后即可进行登录操作 混合开发中，应该将token值写入隐藏表单域并跟随表单数据一同提交 前后端分离开发中，应该将令牌设置到RequestHeader中与JSON请求体一同提交 十一、Oauth2协议 简介：第三方认证技术方案最主要是用来解决认证协议的通用标准问题，因为要实现跨系统认证，各系统之间一定要遵循一定的接口协议 Oauth协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时，任何第三方都可以使用Oauth认证服务，人和服务提供商都可以实现自身的Oauth认证服务，业界提供了Oauth的多种语言（PHP、JavaScript、Java、Ruby等）实现的SDK，大大节约我们的开发时间。 Oauth目前发展到2.0版本，已得到广泛应用 1. 基本概念 常用术语 客户凭证（client credentials）：客户端的clientId和密码用于认证客户 令牌（tokens）：授权服务器在接受到客户请求后，颁发的访问令牌 作用域（scopes）：客户请求访问令牌时，由资源拥有者额外指定的细分权限（permission） 令牌类型 授权码：仅用于授权码授权类型，用于交换获取访问令牌和刷新令牌 访问令牌：用于代表一个用户或服务直接去访问受保护的资源 刷新令牌：用于去授权服务器获取一个刷新访问令牌 BearerToken：不管谁拿到Token都可以访问资源 Proof of Possession Token：可以校验client是否对Token由明确的一拥有权 优点 更安全，客户端不接触用户密码，服务器更易集中保护 广泛传播被持续采用 短寿命和封装的token 资源服务器与授权服务器解耦 集中式授权，简化客户端 易于请求和传递 考虑多种客户端架构场景 客户可以具有不同的信任级别 缺点： 协议框架太宽泛，造成各种实现的兼容性和互操作性差 不是一个认证协议，本身并不能告诉你任何用户信息 2. 授权模式 ①. 授权码模式 ②. 简化授权模式 ③. 密码模式 ④. 客户端模式 ⑤. 刷新令牌 3. SpringSecurity Oauth2架构 ①. 授权服务器 Authorize Endpoint：授权端点，进行授权 Token Endpoint：令牌端点，经过授权拿到对应token Introspection Endpoint：校验端点，校验token合法性 Revocation Endpoint：撤销端点，撤销授权 ②. SpringSecurityOauth2流程 用户访问此时没有Token，Oauth2RestTemplate会报错，这个报错信息会被Oauth2ClientContextFilter捕获 认证服务器通过Authorization Endpoint进行授权，并通过AuthorizationServerTokenServices生成授权码并返回给客户端 客户端拿到授权码去认证服务器通过Token Endpoint调用AuthorizationServerTokenServices生成Token并返回给客户端 客户端拿到Token去资源服务器去访问资源，一般会通过Oauth2AuthenticationManager调用ResourceServerTokenServices进行校验，校验通过可以获取资源 ③. 快速入门 引入依赖 &lt;!--管理spring-cloud版本--&gt; &lt;dependencyManagement&gt; &lt;dependencies&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt; &lt;version&gt;Hoxton.SR12&lt;/version&gt; &lt;type&gt;pom&lt;/type&gt; &lt;scope&gt;import&lt;/scope&gt; &lt;/dependency&gt; &lt;/dependencies&gt; &lt;/dependencyManagement&gt; &lt;!--引入对应依赖--&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-starter-security&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-starter-oauth2&lt;/artifactId&gt; &lt;/dependency&gt; 十二、JWT 1. 常见的认证机制 HTTP Basic Auth Cookie Auth OAuth Token Auth 2. Token Auth 优点 支持跨域访问 无状态，服务端无需存储session信息，只需在客户端cookie中存储token值 更适用于CDN 解耦，不需要绑定到一个特定的身份验证方案 适用于移动端应用，原生的移动应用是不支持cookie的 CSRF：不再依赖Cookie，所以不需要考虑CSRF的防护 性能相比于CookieAuth更快，因为只需要对token进行验证和解析 不需要再为登录页面做特殊处理 基于标准化 跨语言 轻量级JSON风格参数 缺点 无法更新token有效期 无法销毁一个token 3. 什么是JWT JSON Web Token是一个开放的行业标准（RFC7519），它定义了一种简洁的、自包含的协议格式，用于在统信双方传递json对象，传递信息经过数字签名可以被验证和信任，JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名，防止被篡改 官网：https://jwt.io 标准：https://tools.ietf.org/html/rfc7519 3.1 JWT的优点 jwt基于json，非常方便解析 可以在令牌中自定义丰富的内容，易扩展 通过非对称加密算法及数字签名技术，可以防篡改，安全性高 资源服务使用JWT可以不依赖认证服务完成授权 4. JWT组成 一个JWT实际上就是一个字符串，它由三部分组成，头部、负载与签名。 header（base64）.payload（base64）.singature（HS256） 4.1 头部 用于描述关于当前JWT的最基本信息，例：签名所使用的算法 { &quot;alg&quot;:&quot;HS256&quot;, &quot;typ&quot;:&quot;JWT&quot; } typ：类型 alg：签名算法 Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64，所以每6bit为一个单元，对应某个可打印字符。3个字节有24bit，对应4个Base64单元，即3个字节需要用4个可打印字符来表示，JDK中提供了非常方便的BASE64Encoder和BASE64Decoder，用他们可以非常方便的完成BASE64的编码和解码。BASE64是一个对称编码，所以JWT头部信息在被BASE64编码后并不安全 4.2 负载（Payload） 用于存放有效信息，其实就是内容，内容包含以下3个部分 标准中注册的声明 iss：jwt签发者 sub：jwt所面向的用户 aud：接收jwt的一方 exp：jwt过期时间，必须大于签发时间 nbf：定义在何时之前jwt是不可用的 iat：jwt签发时间 jti：jwt的唯一身份标识，用来作为一次性token，回避重放攻击 公共的声明 可以添加任何信息，一般用来添加用户信息或业务信息，不要存放敏感信息，因为客户端可解密 私有的声明 是提供者和消费者共同定义的声明，一般不建议存放敏感信息，因为客户端可解密 4.3 签证（Signature） jwt的第三部分是一个签证信息，签证信息由以下三个部分组成： header（BASE64加密） payload（BASE64加密） secret（秘钥） secret是存储在服务器上的，jwt的签发也是在服务器端，secret就是用来进行jwt的签发和jwt的验证的，这个秘钥是保密的，如果泄露，客户端就可以自行签发jwt 5. 快速入门 5.1 引入依赖 &lt;dependency&gt; &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt; &lt;artifactId&gt;jjwt&lt;/artifactId&gt; &lt;version&gt;0.9.0&lt;/version&gt; &lt;/dependency&gt; 5.2 书写工具类 package com.zhiyuan.security.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.servlet.http.HttpServletRequest; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import static org.springframework.util.StringUtils.isEmpty; /** * @author 絷缘 * @date 2023-05-14 * @Description JWT工具类 */ public class JWTUtils { //默认的Token过期时间 public static final Long EXPIRE_TIME = 7*24*60*60L; //JWT秘钥 public static final String SECRET = &quot;4C5C8A178EE84933B04D21249185EDEB&quot;; public static final String SEPARATOR_EMPTY = &quot;&quot;; public static final String SEPARATOR_SHORT_LINE = &quot;-&quot;; /** * 生成UUID * @param upperCase 是否转大写 * @return 返回UUID字符串 */ public static String UUID(boolean upperCase){ String UUID_STR = UUID.randomUUID().toString().replace(SEPARATOR_SHORT_LINE, SEPARATOR_EMPTY); if(upperCase){ return UUID_STR.toUpperCase(); } return UUID_STR.toLowerCase(); } /** * 生成JWT * @param uid 负载信息userId * @param username 负载信息username * @return 返回JWT字符串 */ public static String generateJWT(String uid,String username){ HashMap&lt;String, Object&gt; headerMap = new HashMap&lt;&gt;(); headerMap.put(&quot;alg&quot;,&quot;HS256&quot;); headerMap.put(&quot;typ&quot;,&quot;JWT&quot;); HashMap&lt;String, Object&gt; claimMap = new HashMap&lt;&gt;(); claimMap.put(&quot;uid&quot;,uid); claimMap.put(&quot;username&quot;,username); String jwt = Jwts.builder() //设置头部信息 .setHeader(headerMap) //设置负载信息 .setIssuer(&quot;zhiyuan&quot;) .setId(UUID(true)) .setSubject(&quot;cloud-drive&quot;) .setIssuedAt(new Date()) .setClaims(claimMap) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME)) //设置签名信息 .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); return jwt; } /** * 生成JWT * @param id 传入jti * @param subject 传入sub * @param issuedAt 传入iat * @param claimMap 传入自定义声明claim * @param expireTime 传入exp过期时间 * @return 返回JWT字符串 */ public static String generateJWT(String id, String subject, Date issuedAt,Map claimMap,Long expireTime){ HashMap&lt;String, Object&gt; headerMap = new HashMap&lt;&gt;(); headerMap.put(&quot;alg&quot;,&quot;HS256&quot;); headerMap.put(&quot;typ&quot;,&quot;JWT&quot;); String jwt = Jwts.builder() //设置头部信息 .setHeader(headerMap) //设置负载信息 .setIssuer(&quot;zhiyuan&quot;) .setId(id) .setSubject(subject) .setIssuedAt(issuedAt) .setClaims(claimMap) .setExpiration(new Date(System.currentTimeMillis() + expireTime)) //设置签名信息 .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); return jwt; } /** * 验证Token是否有效 * @param jwt 传入jwt字符串 * @return 有效返回true，无效返回false */ public static boolean isValid(String jwt){ if (isEmpty(jwt)){ return false; } try{ Jwts.parser().setSigningKey(SECRET).parseClaimsJws(jwt); }catch (Exception e){ System.err.println(e.getMessage()); return false; } return true; } /** * 验证Token是否有效 * @param request 传入HttpServletRequest对象 * @return 有效返回true，无效返回false */ public static boolean isValid(HttpServletRequest request){ String token = request.getHeader(&quot;token&quot;); if(isEmpty(token)){ return false; } try{ Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); }catch (Exception e){ System.err.println(e.getMessage()); return false; } return true; } /** * 获取Token负载内容字符串 * @param request 传入HttpServletRequest对象 * @param claimName 传入负载内容key * @return 返回负载内容key对应的负载内容value */ public static String getClaimInfoForString(HttpServletRequest request,String claimName){ String token = request.getHeader(&quot;token&quot;); if(isEmpty(token)){ return &quot;token不存在&quot;; } try{ Jws&lt;Claims&gt; claimsJws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); Claims body = claimsJws.getBody(); return body.get(claimName,String.class); }catch (Exception e){ System.err.println(e.getMessage()); return &quot;token已失效&quot;; } } /** * 获取Token负载内容对象 * @param request 传入HttpServletRequest对象 * @param claimName 传入负载内容key * @param tClass 传入负载内容value类型 * @return 返回负载内容key对应的负载内容value * @param &lt;T&gt; */ public static &lt;T&gt; T getClaimInfoForObject(HttpServletRequest request,String claimName,Class&lt;T&gt; tClass) { String token = request.getHeader(&quot;token&quot;); if(isEmpty(token)){ return null; } try{ Jws&lt;Claims&gt; claimsJws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); Claims body = claimsJws.getBody(); return body.get(claimName,tClass); }catch (Exception e){ System.err.println(e.getMessage()); return null; } } } 5.3 Redis工具类 import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; /** * spring redis 工具类 **/ @SuppressWarnings(value = { &quot;unchecked&quot;, &quot;rawtypes&quot; }) @Component public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象，Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @return 缓存的对象 */ public &lt;T&gt; ValueOperations&lt;String, T&gt; setCacheObject(String key, T value) { ValueOperations&lt;String, T&gt; operation = redisTemplate.opsForValue(); operation.set(key, value); return operation; } /** * 缓存基本的对象，Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 * @return 缓存的对象 */ public &lt;T&gt; ValueOperations&lt;String, T&gt; setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) { ValueOperations&lt;String, T&gt; operation = redisTemplate.opsForValue(); operation.set(key, value, timeout, timeUnit); return operation; } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public &lt;T&gt; T getCacheObject(String key) { ValueOperations&lt;String, T&gt; operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public void deleteObject(String key) { redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection */ public void deleteObject(Collection collection) { redisTemplate.delete(collection); } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public &lt;T&gt; ListOperations&lt;String, T&gt; setCacheList(String key, List&lt;T&gt; dataList) { ListOperations listOperation = redisTemplate.opsForList(); if (null != dataList) { int size = dataList.size(); for (int i = 0; i &lt; size; i++) { listOperation.leftPush(key, dataList.get(i)); } } return listOperation; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public &lt;T&gt; List&lt;T&gt; getCacheList(String key) { List&lt;T&gt; dataList = new ArrayList&lt;T&gt;(); ListOperations&lt;String, T&gt; listOperation = redisTemplate.opsForList(); Long size = listOperation.size(key); for (int i = 0; i &lt; size; i++) { dataList.add(listOperation.index(key, i)); } return dataList; } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public &lt;T&gt; BoundSetOperations&lt;String, T&gt; setCacheSet(String key, Set&lt;T&gt; dataSet) { BoundSetOperations&lt;String, T&gt; setOperation = redisTemplate.boundSetOps(key); Iterator&lt;T&gt; it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public &lt;T&gt; Set&lt;T&gt; getCacheSet(String key) { Set&lt;T&gt; dataSet = new HashSet&lt;T&gt;(); BoundSetOperations&lt;String, T&gt; operation = redisTemplate.boundSetOps(key); dataSet = operation.members(); return dataSet; } /** * 缓存Map * * @param key * @param dataMap * @return */ public &lt;T&gt; HashOperations&lt;String, String, T&gt; setCacheMap(String key, Map&lt;String, T&gt; dataMap) { HashOperations hashOperations = redisTemplate.opsForHash(); if (null != dataMap) { for (Map.Entry&lt;String, T&gt; entry : dataMap.entrySet()) { hashOperations.put(key, entry.getKey(), entry.getValue()); } } return hashOperations; } /** * 获得缓存的Map * * @param key * @return */ public &lt;T&gt; Map&lt;String, T&gt; getCacheMap(String key) { Map&lt;String, T&gt; map = redisTemplate.opsForHash().entries(key); return map; } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection&lt;String&gt; keys(String pattern) { return redisTemplate.keys(pattern); } } 5.4 Redis序列化类 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.Charset; public class FastJsonRedisSerializer&lt;T&gt; implements RedisSerializer&lt;T&gt; { public static final Charset DEFAULT_CHARSET = Charset.forName(&quot;UTF-8&quot;); private Class&lt;T&gt; clazz; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } public FastJsonRedisSerializer(Class&lt;T&gt; clazz){ super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length &lt;= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return (T) JSON.parseObject(str, clazz); } } 5.5 Redis配置类 import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean @SuppressWarnings(value = {&quot;unchecked&quot;,&quot;rawtypes&quot;}) public RedisTemplate&lt;Object, Object&gt; redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate&lt;Object, Object&gt; redisTemplate = new RedisTemplate&lt;&gt;(); redisTemplate.setConnectionFactory(redisConnectionFactory); // 使用 FastJsonRedisSerializer 替换默认序列化 FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class); // 设置key和value的序列化规则 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(fastJsonRedisSerializer); // 设置hashKey和hashValue的序列化规则 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(fastJsonRedisSerializer); // 设置支持事物 redisTemplate.setEnableTransactionSupport(true); redisTemplate.afterPropertiesSet(); return redisTemplate; } } 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/fgzXm9vap/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/fgzXm9vap/"},{"title":"Vue2复习&Vue3学习","content":"一、Vue简介 1. Vue特点 采用组件化模式，提高代码复用率让代码更好维护 声明式编码，无需操作DOM，提高开发效率 使用虚拟DOM+Diff算法，尽量复用DOM节点 2. 起步 初学者不建议使用vue-cli安装，请用script标签引入 &lt;script src=&quot;https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js&quot;&gt;&lt;/script&gt; 下载地址 开发版本：https://cn.vuejs.org/js/vue.js 生产版本：https://cn.vuejs.org/js/vue.min.js 3. 初识 &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta charset=&quot;utf-8&quot;&gt; &lt;title&gt;Vue学习&lt;/title&gt; &lt;script src=&quot;./js/vue.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div id=&quot;root&quot;&gt; &lt;h2&gt;{{username}}&lt;/h2&gt; &lt;h2&gt;{{nickname}}&lt;/h2&gt; &lt;h2&gt;{{age}}&lt;/h2&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; Vue.config.productionTip = false; new Vue({ el:&quot;#root&quot; ,//绑定页面DOM data:{ username:&quot;zhiyuan&quot;, nickname:&quot;絷缘&quot;, age:26 }, }); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; Vue中一个实例对应一个页面DOM容器，一个DOM容器无法对应多个实例，一个实例也不能绑定多个DOM容器 4. 模板语法 4.1 插值语法（插值表达式） ①. 插值表达式 格式：{{ 表达式 }} 注意：插值表达式中必须是JS代码或JS表达式或Vue实例中声明的data属性 4.2 指令语法（属性绑定） ②. 属性绑定 格式：v-bind:[attributeName] 例 &lt;div id=&quot;root&quot;&gt; &lt;img v-bind:src=&quot;avatar&quot;/&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; new Vue({ el:&quot;#root&quot;, data:{ avatar:&quot;https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&amp;size=100&quot; } }); &lt;/script&gt; 页面效果 简写：:[attributeName] 例 &lt;div id=&quot;root&quot;&gt; &lt;img :src=&quot;avatar&quot;/&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; new Vue({ el:&quot;#root&quot;, data:{ avatar:&quot;https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&amp;size=100&quot; } }); &lt;/script&gt; ②. 双向绑定 格式：v-model:value 例 &lt;div id=&quot;root&quot;&gt; &lt;img :src=&quot;avatar&quot;/&gt; &lt;input type=&quot;text&quot; v-model:value=&quot;avatar&quot;/&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; new Vue({ el:&quot;#root&quot;, data:{ avatar:&quot;https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&amp;size=100&quot; } }); &lt;/script&gt; 如下代码是错误的，因为v-model只能应用表单类元素上，我们认为只有可以被改变value的元素双向绑定才有意义 简写：v-model v-model默认绑定就是value属性 5. 容器绑定两种方式 5.1 方式一：el绑定 new Vue({ el:&quot;DOM元素标志&quot;,//使用el挂载DOM容器 data:{ key:value, ...... } }); 5.2 方式二：mount挂载 const vm = new Vue({ data:{ key:value, ...... } }); vm.$mount('DOM元素标志');//使用实例对象挂载DOM容器 6. data属性的两种写法 6.1 方式一：对象形式 new Vue({ el:&quot;#container&quot;, data:{ key1:value1, key2:value2, key...:value... } }); 6.2 方式二：函数形式 new Vue({ el:&quot;#container&quot;, data:function(){ return { key1:value1, key2:value2, key...:value... } } //简写 data(){ return { key1:value1, key2:value2, key...:value... } } }); 注意：函数方式返回data数据时，不可使用箭头函数，因为箭头函数没有自己的this对象，会向上寻找父类的this，就会拿到window对象，但是我们书写返回函数需要被Vue对象所调用，故而不能使用箭头函数 应用场景：组件封装时，我们必须使用函数式写法返回data值 二、MVVM简介 1. MVVM模型组成 1.1 M：模型（Model） 对应data中的数据 1.2 V：视图（View） 对应模板渲染 1.3 VM：视图模型 对应Vue实例对象 2. Object.defineProperty 2.1 ES6为对象添加属性 Object.defineProperty（originObject，attributeName，attributeValue） let person = { name:&quot;zhiyuan&quot;, gender:&quot;male&quot; } Object.defineProperty(person,'age',{ value:18 }); 注意：通过defineProperty直接添加的对象属性，是无法进行枚举的，即遍历对象无法取得该值，且我们无法修改或删除这个属性 如何直观的看到？ Object.keys（object） 使用keys函数可以将指定对象的key值抽取为一个数组，我们会发现新加的属性没有出现在数组中 如何解决这个问题？ Object.defineProperty(person,'age',{ value:18, enumerable:true, //将enumrable置为true，就可以对其进行枚举了 //为了方便使用，我们将writable,configurable属性也置为true，这样就可以对该属性进行修改删除了 writable:true, configurable:true }); 为属性增加getter和setter方法 let age = 18; let user = { username:&quot;zhiyuan&quot;, nickname:&quot;絷缘&quot;, password:&quot;123456&quot;, gender:&quot;male&quot; } Object.defineProperty(user,'age',{ get:function(){ return age; }, set:function(age){ this.age = age; } }); 2.2 数据代理 概念：指的是在访问或者修改对象的某个属性时，通过一段代码拦截这个行为，进行额外的操作或者修改返回结果。 Vue实例中的data就是实现了数据代理，data在Vue实例中实际存在的属性名称为_data 3. 事件处理 3.1 methods const vm = new Vue({ el:&quot;#container&quot;, data:{ user:{ username:&quot;zhiyuan&quot;, nickname:&quot;絷缘&quot;, password:&quot;123456&quot;, gender:&quot;male&quot;, age:18 } }, methods:{ methodName(event){ //methodContent console.log(this===vm) //true 此处this为vm实例对象 } } }}; 3.2 事件绑定 格式：v-on:[event] &lt;div id=&quot;container&quot;&gt; &lt;button v-on:click=&quot;showInfo($event)&quot;&gt;按钮&lt;/button&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;#container&quot;, data:{ user:{ username:&quot;zhiyuan&quot;, nickname:&quot;絷缘&quot;, password:&quot;123456&quot;, gender:&quot;male&quot;, age:18 } }, methods:{ showInfo(event){ alert(&quot;按钮被点击&quot;); } } }); &lt;/script&gt; 简写：@[event] 例：v-on:click == @click &lt;button @click=&quot;showMessage($event,传递参数)&quot;&gt;点我提示信息&lt;/button&gt; new Vue({ el:&quot;#root&quot;, data:{}, methods:{ showMessage(event,传递参数){ console.log(event.target.innerText,传递参数); } } }); 3.3 事件修饰符 阻止默认行为：.prevent 阻止事件冒泡：.stop 事件只触发一次：.once 使用事件捕获模式：.capture 只有event.target是当前操作的元素时才出发事件：.self 立即执行事件默认行为，无需等待事件回调执行完毕：.passive @wheel：鼠标滚轮滚动事件 @scroll：滚动条滚动事件 3.4 键盘事件 Vue中提供的常用按键别名 enter ：回车键 delete ：退格键和删除键 esc ：ESC键 space ：空格键 tab ：Tab键 up ：方向键上 down ：方向键下 left ：方向键左 right ：方向键右 Vue未提供别名的按键，可以使用原始key值绑定，但要注意转换为短横线命名方式，如：caps-lock 注意：tab键在@keyup事件中无效，因为tab键默认会切换焦点区域，必须配合@keydown才能正常使用 系统修饰键 ctrl alt shift win：windows徽标键 注意：系统修饰键配合@keydown才可以正常触发事件，如果是@keyup需要配合其他按键才能正常触发 Vue中自定义按键别名 Vue.config.keyCodes.huiche = 13; 4. 计算属性 4.1 基本概念 所谓的计算属性就是用几个data中已有的属性进行处理计算后得到一个全新的属性 const vm = new Vue({ el:&quot;#container&quot;, data:{ firstName:&quot;絷缘&quot;, lastName:&quot;解&quot; }, methods:{}, computed:{ fullName:{ get(){ return this.firstName + &quot;-&quot; + this.lastName; }, set(value){ cosnt array = value.split(&quot;-&quot;); this.firstName = array[0]; this.lastName = array[1]; } } } }) 4.2 getter和setter调用时机 get调用时机 初次读取计算属性时 计算属性所依赖的其他属性发生变化时 set调用时机 当计算属性被修改时 4.3 简写形式 简写 应用场景：计算属性只读不写，默认当做getter使用 const vm = new Vue({ el:&quot;#container&quot;, data:{ firstName:&quot;絷缘&quot;, lastName:&quot;解&quot; }, methods:{}, computed:{ fullName(){ return this.firstName + &quot;-&quot; + this.lastName; } } }) 5. 监视属性 5.1 在watch属性中书写监视内容 监视属性不仅可以监视data中声明的属性，还可以监视计算属性 const vm = new Vue({ el:&quot;#container&quot;, data:{ isLogin:true, }, methods:{}, computed:{}, watch:{ 'isLogin':{ //初始化时让handler执行一次 immediate:true, //当被监视属性改变时会调用handler方法 handler(newValue,oldValue){ console.log(isLogin==true?&quot;已登录&quot;:&quot;未登录&quot;); } } } }); 5.2 vm实例调用$watch方法书写监视内容 vm.$watch('isLogin',{ //初始化时让handler执行一次 immediate:true, //当被监视属性改变时会调用handler方法 handler(newValue,oldValue){ console.log(isLogin==true?&quot;已登录&quot;:&quot;未登录&quot;); } }) 5.3 深度监视 Vue的watch默认不监视对象内部值的改变，要想让watch可以监视到对象内部属性的变化，需要配置deep:true watch中默认监视外层对象，无法监视多级结构的内容改变，当外层对象改变才会执行handler，内部成员的改变不会触发handler，如果我们想要监视内部成员的改变，需要在watch对应监视属性中添加 deep:true const vm = new Vue({ el:&quot;#container&quot;, data:{ user:{ username:&quot;zhiyuan&quot;, gender:&quot;male&quot; } } watch:{ 'user':{ deep:true, handler(){ console.log(&quot;user被改变&quot;); } } } }) 此时我们改变user中的属性值，handler也会被触发执行。 5.4 简写形式 watch使用简写方式的代价就是无法进行除了handler方法外的配置，比如深度监视，比如初始化执行 const vm = new Vue({ el:&quot;#container&quot;, data:{ isLogin:true } watch:{ isLogin(newValue,oldValue){ console.log('isLogin被修改',newValue,oldValue); } } }) 6. 计算属性与监视属性的区别 6.1 区别 computed可以完成的功能，watch都可以完成 watch可以完成的功能，computed不一定能够完成，例如watch可以进行异步操作 6.2 注意事项 注意： 被Vue所管理的函数应该写成普通函数，这样this的指向才是vue实例对象 所有不被Vue管理的函数（定时器回调函数，ajax回调函数，Promise的回调函数等），最好写成箭头函数，这样this的指向才是vue实例对象，否则它们将指向约定的this为window对象 7. 绑定样式 7.1 绑定class 步骤 先定义好对应选择器的样式 将选择器的名称声明到data数据中 将data数据中的选择器名称与class绑定 7.2 绑定style ①. 写法一 &lt;div id=&quot;container&quot; :style=&quot;{fontSize:fontSize+'px'}&quot;&gt; 内容 &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;#container&quot;, data:{ fontSize:40 } }); &lt;/script&gt; ②. 写法二 &lt;div id=&quot;container&quot; :style=&quot;styleObj&quot;&gt; 内容 &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;#container&quot;, data:{ styleObj:{ fontSize:'40px', color:'#666' } } }); &lt;/script&gt; ③. 写法三 &lt;div id=&quot;container&quot; :style=&quot;[styleObj1,styleObj2]&quot;&gt; 内容 &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;#container&quot;, data:{ styleObj1:{ fontSize:'40px' }, styleObj2:{ color:'#666' } } }); &lt;/script&gt; 8. 条件渲染 8.1 v-if &lt;div class=&quot;container&quot;&gt; &lt;h2 v-if=&quot;isLogin === false&quot;&gt;登录注册&lt;/h2&gt; &lt;h2 v-else&gt;已登录&lt;/h2&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;.container&quot;, data:{ isLogin:false } }); &lt;/script&gt; 8.2 v-show &lt;div class=&quot;container&quot;&gt; &lt;h2 v-show=&quot;isLogin === false&quot;&gt;登录注册&lt;/h2&gt; &lt;h2 v-show=&quot;isLogin&quot;&gt;已登录&lt;/h2&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;.container&quot;, data:{ isLogin:false } }); &lt;/script&gt; 8.3 v-if与v-show的区别 区别 v-if 适用于切换频率比较低的场景，因为它是通过DOM渲染节点来完成的显示与隐藏，当不满足条件时，页面元素会被移除，满足条件时又会被创建，所以当大量的元素需要频繁切换的时候，v-if不是很好的解决方案 v-show 适用于切换频率较高的场景，因为它是通过元素的css样式 display:none 属性来实现元素的显示与隐藏的，当满足条件时， display:none 被移除，元素显示，不满足条件时， display:none 被添加，元素隐藏 8.4 条件相同时条件渲染书写方式 例 &lt;div class=&quot;container&quot;&gt; &lt;h2 v-if=&quot;isLogin === false&quot;&gt;登录&lt;/h2&gt; &lt;h2 v-if=&quot;isLogin === false&quot;&gt;注册&lt;/h2&gt; &lt;/div&gt; &lt;!--v-if条件相同重复书写，进一步优化--&gt; &lt;div class=&quot;container&quot;&gt; &lt;div v-if=&quot;isLogin === false&quot;&gt; &lt;h2&gt;登录&lt;/h2&gt; &lt;h2&gt;注册&lt;/h2&gt; &lt;/div&gt; &lt;/div&gt; 上面的优化虽然实现了效果也避免了条件渲染重复，但是加入了新的没有必要的页面元素，一定程度影响了布局，不是非常合适，那么有没有一种办法解决这个问题呢？ Vue为我们提供了一个名为 &lt;template&gt; 的标签，这个标签可以与条件渲染 v-if 结合使用而且不影响页面结构，注意只能与 v-if 结合使用 9. 列表渲染 9.1 基本列表 &lt;div class=&quot;container&quot;&gt; &lt;!-- 遍历数组 --&gt; &lt;ul class=&quot;list&quot;&gt; &lt;li class=&quot;list-item&quot; v-for=&quot;(user,index) in userList&quot; :key=&quot;user.uid&quot;&gt; {{ user.uid + &quot;-&quot; + user.username + &quot;-&quot; + user.age}} &lt;/li&gt; &lt;/ul&gt; &lt;!-- 遍历对象 --&gt; &lt;ul class=&quot;list&quot;&gt; &lt;li class=&quot;list-item&quot; v-for=&quot;(value,key) of admin&quot; :key=&quot;key&quot;&gt; {{ key + &quot;-&quot; + value }} &lt;/li&gt; &lt;/ul&gt; &lt;!-- 遍历字符串 --&gt; &lt;ul class=&quot;list&quot;&gt; &lt;li class=&quot;list-item&quot; v-for=&quot;(char,index) of username&quot; :key=&quot;index&quot;&gt; {{ index + &quot;-&quot; + char }} &lt;/li&gt; &lt;/ul&gt; &lt;!-- 遍历指定次数 --&gt; &lt;ul class=&quot;list&quot;&gt; &lt;li class=&quot;list-item&quot; v-for=&quot;(number,index) of 5&quot;&gt; {{ index + &quot;-&quot; + number }} &lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:'.container', data:{ userList:[ {uid:'001',username:'zhiyuan001',age:18}, {uid:'002',username:'zhiyuan002',age:20}, {uid:'003',username:'zhiyuan003',age:23}, ], admin:{ username:&quot;zhiyuan&quot;, role:&quot;admin&quot;, nickname:&quot;絷缘&quot;, email:&quot;1234567890@gmail.com&quot; }, username:&quot;zhiyuan&quot; } }); &lt;/script&gt; 9.2 key的作用 之前代码中，我们使用index作为key，其实这是不正确的，虽然当前我们确实没有发现问题，但是当我们对原有数据进行打乱顺序的操作后，如果继续以index作为key，那么会导致vue对DOM元素的复用错乱，进而导致页面数据错乱的结果。 比如：当我们往数组的头部追加一项内容时，列表渲染会按索引与之前的DOM进行比较，查看是否可复用，但是我们现在的DOM元素的顺序已经发生了改变，在对其进行比较复用，就会导致元素和数据的错乱。 那么我们不写怎么样？ Vue会默认将当前数据的索引值作为当前DOM绑定的key值，所以造成的结果是一样的。 正确的key值绑定 key值一定是可以唯一标识当前元素的信息，例如数据的ID值，这样无论我们变换顺序或者做其它操作，都不会对最终列表渲染的结果产生影响。 key的作用 key是虚拟DOM对象的标识，当数据发生变化时，Vue会根据新的数据生成新的虚拟DOM，随后与之前的虚拟DOM进行差异比较，比较规则如下： 旧的虚拟DOM中找到了与新的虚拟DOM相同的key 若是虚拟DOM的内容没有发生改变，直接使用旧的虚拟DOM所生成的旧的真实DOM元素 若是虚拟DOM的内容已经发生改变，则由新的虚拟DOM生成新的真实DOM元素 旧的虚拟DOM中未找到与新的虚拟DOM相同的key 根据新的虚拟DOM创建新的真实DOM元素 10. 列表过滤 10.1 方式一：监视属性 &lt;div class=&quot;container&quot;&gt; &lt;input type=&quot;text&quot; v-model=&quot;keyWord&quot; placeholder=&quot;搜索...&quot;&gt; &lt;ul class=&quot;list&quot;&gt; &lt;li class=&quot;list-item&quot; v-for=&quot;(user,index) in tempList&quot; :key=&quot;user.uid&quot;&gt; {{ user.uid + &quot;-&quot; + user.username + &quot;-&quot; + user.age}} &lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;.container&quot;, data:{ userList:[ {uid:'001',username:'胡歌',age:18,gender:'male'}, {uid:'002',username:'霍建华',age:20,gender:'male'}, {uid:'003',username:'焦恩俊',age:23,gender:'male'}, {uid:'004',username:'胡一菲',age:20,gender:'female'}, {uid:'005',username:'胡图图',age:6,gender:'male'}, {uid:'006',username:'刘诗诗',age:21,gender:'female'}, {uid:'007',username:'刘亦菲',age:22,gender:'female'}, {uid:'008',username:'刘德华',age:22,gender:'male'} ], keyWord:'', tempList:[] }, watch:{ 'keyWord':{ immediate:true, handler(newValue,OldValue){ this.tempList = this.userList.filter((user)=&gt;{ return user.username.indexOf(newVlaue) != -1; }) } } } }); &lt;/script&gt; 10.2 方式二：计算属性 &lt;div class=&quot;container&quot;&gt; &lt;input type=&quot;text&quot; v-model=&quot;keyWord&quot; placeholder=&quot;搜索...&quot;&gt; &lt;ul class=&quot;list&quot;&gt; &lt;li class=&quot;list-item&quot; v-for=&quot;(user,index) in tempList&quot; :key=&quot;user.uid&quot;&gt; {{ user.uid + &quot;-&quot; + user.username + &quot;-&quot; + user.age}} &lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;.container&quot;, data:{ userList:[ {uid:'001',username:'胡歌',age:18,gender:'male'}, {uid:'002',username:'霍建华',age:20,gender:'male'}, {uid:'003',username:'焦恩俊',age:23,gender:'male'}, {uid:'004',username:'胡一菲',age:20,gender:'female'}, {uid:'005',username:'胡图图',age:6,gender:'male'}, {uid:'006',username:'刘诗诗',age:21,gender:'female'}, {uid:'007',username:'刘亦菲',age:22,gender:'female'}, {uid:'008',username:'刘德华',age:22,gender:'male'} ], keyWord:'' }, computed:{ tempList:{ get(){ return this.userList.filter((user)=&gt;{ return user.username.indexOf(this.keyWord) != -1; }); } } } }); &lt;/script&gt; 11. 列表排序 &lt;div class=&quot;container&quot;&gt; &lt;input type=&quot;text&quot; v-model=&quot;keyWord&quot; placeholder=&quot;搜索...&quot;&gt; &lt;input type=&quot;button&quot; value=&quot;年龄降序&quot; @click=&quot;sortType = 1&quot;&gt; &lt;input type=&quot;button&quot; value=&quot;年龄升序&quot; @click=&quot;sortType = 2&quot;&gt; &lt;input type=&quot;button&quot; value=&quot;原顺序&quot; @click=&quot;sortType = 0&quot;&gt; &lt;ul class=&quot;list&quot;&gt; &lt;li class=&quot;list-item&quot; v-for=&quot;(user,index) in tempList&quot; :key=&quot;user.uid&quot;&gt; {{ user.uid + &quot;-&quot; + user.username + &quot;-&quot; + user.age}} &lt;/li&gt; &lt;/ul&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;.container&quot;, data:{ userList:[ {uid:'001',username:'胡歌',age:18,gender:'male'}, {uid:'002',username:'霍建华',age:20,gender:'male'}, {uid:'003',username:'焦恩俊',age:23,gender:'male'}, {uid:'004',username:'胡一菲',age:20,gender:'female'}, {uid:'005',username:'胡图图',age:6,gender:'male'}, {uid:'006',username:'刘诗诗',age:21,gender:'female'}, {uid:'007',username:'刘亦菲',age:22,gender:'female'}, {uid:'008',username:'刘德华',age:22,gender:'male'} ], keyWord:'', //sortType为0时正常排序，为1时按年龄降序，为2时按年龄升序 sortType:0 }, computed:{ tempList:{ get(){ const arr = this.userList.filter((user)=&gt;{ return user.username.indexOf(this.keyWord)!=-1; }); if(this.sortType){ arr.sort((u1,u2)=&gt;{ return this.sortType===1 ? u2.age-u1.age :u1.age-u2.age; }); } return arr; } } } }); &lt;/script&gt; 12. Vue监测数据的原理 Vue会监视data中所有层次的数据 如何监测对象中的数据？ 通过setter实现监视，且要在new Vue时就传入要监测的数据 对象中后追加的属性，Vue默认不做响应式处理 如需给后添加的属性做响应式，请使用如下API Vue.set(target,propertyName/index,value) this.$set(target,propertyName/index,value) 如何监测数组中的数据？ 通过包裹数组更新元素的方法实现，本质上就是做了两件事 调用原生JS对应的数组方法进行更新 重新解析模板，更新页面 在Vue修改数组中的某个元素一定要用如下方法（即Vue重新包装过的方法）： push pop shift unshift splice sort reverse Vue.set() this.$set() 13. 收集表单数据 v-model的三个修饰符 lazy ：失去焦点再收集数据 number ：输入字符串转为有效的数字 trim：输入首尾空格过滤 收集表单数据注意事项： v-model搜集的是value值，用户的输入就是value值 v-model搜集的是value值，而且需要给标签设置value值 1. 如果没有设置value值，默认搜集checked的值，即true or false 2. 设置了value值 1. data中搜集此项的属性默认值为字符串，那么vue还是会搜集checked的值 2. data中搜集此项的属性默认值为数组，那么vue搜集的就是value组成的数组 14. 过滤器 过滤器 定义 对要展示的数据进行特定格式化后再显示，适用场景，后端商品价格数据，时间戳格式修改为前端展示格式 语法 注册过滤器：Vue.filter(name,callback) 或 new Vue({filters:{...}}) 使用过滤器：{{ 参数 | 过滤器名 }} 或 v-bind:属性名=&quot; 参数 | 过滤器名&quot; 备注 过滤器也可以接受额外参数，多个过滤器可以串联使用，后一个过滤器的参数为前一个过滤器的返回结果 过滤器并没有改变原本的数据，只是产生了格式化后的新数据 14.1 局部过滤器 &lt;div id=&quot;container&quot;&gt; &lt;h3&gt;{{formatTime}}&lt;/h3&gt; &lt;h3&gt;{{getFormatTime()}}&lt;/h3&gt; &lt;h3&gt;{{time|timeFormatFilter('YYYY-MM-DD')}}&lt;/h3&gt; &lt;/div&gt; &lt;script src=&quot;./js/dayjs.min.js&quot;&gt;&lt;/script&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;#container&quot;, data:{ time:1641475263843 }, computed:{ formatTime(){ return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss'); } }, methods:{ getFormatTime(){ return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss'); } }, filters:{ timeFormatFilter(value,formatStr='YYYY-MM-DD HH:mm:ss'){ return dayjs(value).format(formatStr); } } }); &lt;/script&gt; 14.2 全局过滤器 &lt;script type=&quot;text/javascript&quot;&gt; Vue.filter('filterName',function(value){ return value.slice(0,3); }); &lt;/script&gt; 过滤器只能与插值表达式或者v-bind使用，无法与v-model结合使用 15. 其他内部指令 15.1 v-text 向其所在的节点中渲染文本内容，不过它会替换掉节点中已有的内容 &lt;div id=&quot;container&quot;&gt; &lt;div v-text=&quot;text&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;#container&quot;, data:{ text:&quot;文本内容&quot; } }); &lt;/script&gt; 15.2 v-html &lt;div id=&quot;container&quot;&gt; &lt;div v-html=&quot;html&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;#container&quot;, data:{ html:&quot;&lt;h1&gt;html内容&lt;/h1&gt;&quot; } }); &lt;/script&gt; 15.3 v-cloak 当容器被Vue实例接管的时候，自动删除本属性 使用场景：在Vue实例未接管之前，隐藏没有数据的DOM元素 [v-cloak] { display: none; } 15.4 v-once v-once指令在DOM节点初次动态渲染后，就变为静态内容了，之后无论绑定的数据发生何种变化，都不会引起当前内容的更新，用于优化性能 15.5 v-pre v-pre指令会跳过当前节点的编译过程，如果没有使用指令语法、插值表达式的节点，添加v-pre，可以加快编译。使用过的节点则会使所使用的语法失效。 16. 自定义指令 16.1 函数式写法 &lt;div id=&quot;container&quot;&gt; &lt;span v-text=&quot;number&quot;&gt;&lt;/span&gt; &lt;span v-big=&quot;number&quot;&gt;&lt;/span&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; new Vue({ el:&quot;#container&quot;, data:{ number:1 }, directives:{ big(element,bindObject){ element.innerText = bindObject.value * 10; } } }); &lt;/script&gt; 自定义指令函数何时被调用？ 指令与元素成功绑定时，调用一次 指令所在的模板被重新解析时，调用一次 16.2 对象式写法 &lt;div id=&quot;container&quot;&gt; &lt;input type=&quot;text&quot; v-fbind:value=&quot;number&quot;/&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; new Vue({ el:&quot;#container&quot;, data:{ number:1 }, directives:{ fbind:{ //指令与元素成功绑定时被调用 bind(element,bindObject){ element.value = bindObject.value; }, //指令所在元素被插入页面时 insert(element,bindObject){ element.focus(); }, //指令所在的模板被重新解析时 update(element,bindObject){ elelment.value = bindObject.value; } } } }); &lt;/script&gt; 16.3 常见问题 自定义指令命名时不要使用驼峰命名法，要转换为横线命名法，且在声明该指令函数时将函数名用引号包裹，避免Vue解析出错 自定义指令回调函数中的this是window对象，因为是由我们直接操作真实DOM的，Vue不会再管理this 一个Vue实例中的自定义指令只能被当前实例索使用，若想被其他vue实例使用，必须声明为全局指令 16.4 全局自定义指令 &lt;script type=&quot;text/javascript&quot;&gt; Vue.directive('name',{ bind(element,bindObject){ ...... }, inserted(element,bindObject){ ...... }, update(element,bindObject){ ...... } }); &lt;/script&gt; 三、生命周期 所谓生命周期函数就是Vue在一些关键的节点时刻为我们调用的特殊名称的回调函数 1. beforeCreate 刚刚完成初始化生命周期、事件，数据代理未开始，此时无法通过实例访问到data中的数据，methods中的方法 2. created 刚刚完成初始化数据监视、数据代理，此时可以通过实例访问到data中的数据，methods中的方法 3. beforeMount 刚刚完成模板解析，在内存中生成虚拟DOM，还未经Vue编译为真实DOM放入到页面，此时对DOM元素的所有操作最终都无效 4. mounted Vue将虚拟DOM编译为真实DOM元素放入页面后，调用mounted函数 5. beforeUpdate 刚刚完成数据的修改，但是页面还没有改变，此时会调用beforeUpdate函数 6. updated 数据被修改，页面也得到了更新，此时调用updated函数 7. beforeDestory 实例中所有的属性（data、methods等）都处于可用状态，马上要执行销毁过程，调用beforeDestory函数，一般在此时：关闭定时器、取消订阅消息、解绑自定义事件等收尾操作 注意：虽然此时所有的属性都可用，但对属性的更新不会再触发页面更新 8. destoryed 实例已经被销毁，实例的监听器，与其关联的子组件以及自定义事件全部被销毁 注意： 销毁后借助Vue开发工具看不到任何信息 销毁后所有自定义的事件失效，但是原生DOM事件仍然有效 一般不会在beforeDestory中操作数据，因为即使操作数据，也不会再触发更新了 四、组件化编程 组件：实现应用中局部功能代码和资源的集合 组件中的data必须为函数形式，避免引用数据相互影响导致页面混乱 组件名注意事项： 一个单词组成 首字母小写，即全小写 首字母大写 多个单词组成 短横线命名 所有单词首字母大写（需要Vue脚手架支持） 注册组件时，尽量回避HTML原有的标签名，否则可能出现意想不到的错误，可以使用name属性在创建组件时指定组件名称 在页面中使用组件的方式： &lt;componentName&gt;&lt;/componentName&gt; &lt;componentName/&gt; （需要Vue脚手架支持） 1. 非单文件组件 1.1 创建组件 &lt;script type=&quot;text/javascript&quot;&gt; const headerNavigation = Vue.extend({ template:`&lt;h1&gt;{{title}}&lt;/h1&gt;`, data(){ return { title:&quot;标题&quot; } }, methods:{} }); &lt;/script&gt; 1.2 注册组件 局部注册 &lt;script type=&quot;text/javascript&quot;&gt; const vm = new Vue({ el:&quot;#container&quot;, components:{ 'header-navigation':headerNavigation } }); &lt;/script&gt; 全局注册 &lt;script type=&quot;text/javascript&quot;&gt; Vue.component('header-navigation',headerNavigation); &lt;/script&gt; 1.3 使用组件 &lt;body&gt; &lt;div id=&quot;container&quot;&gt; &lt;header-navigation&gt;&lt;/header-navigation&gt; &lt;/div&gt; &lt;/body&gt; 2. VueComponent构造函数 组件本质是一个名为 VueComponent 的构造函数，是由 Vue.extend 生成的 每次调用 Vue.extend 返回的都是一个全新的 VueComponent 在创建组件的过程中，this 是 VueComponent 实例对象 VueComponent.propertype.__proto__ === Vue.propertype 3. 单文件组件 3.1 示例代码 &lt;template&gt; &lt;!--组件结构--&gt; &lt;div class=&quot;school&quot;&gt; &lt;h1&gt;SchoolVueComponent&lt;/h1&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; //组件交互相关代码 export default { // eslint-disable-next-line vue/multi-word-component-names name: 'School', data(){ return { schoolName: 'IMOOC', address: 'beijing' } }, methods:{}, computed:{}, watch:{}, mixins:{} } &lt;/script&gt; &lt;style&gt; //组件样式 *{ padding:0; margin:0; } &lt;/style&gt; 五、VueCLI 1. 准备工作 1.1 安装NodeJS 下载地址：https://nodejs.org/dist/v16.13.2/node-v16.13.2-x64.msi 1.2 安装NPM 换源 方式一 npm install -g nrm nrm ls nrm use taobao 方式二 npm config set registry https://registry.npm.taobao.org npm config get registry 方式三：临时指定下载源 npm --registry https://registry.npm.taobao.org install [name] 方式三 npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm install [name] 1.3 设置自定义缓存目录和模块下载安装目录 npm config set prefix E:\\Development\\NodeJS\\npm_prefix npm config set cache E:\\Development\\NodeJS\\npm_cache 1.4 安装VueCLI npm install -g @vue/cli 1.5 创建Vue项目 vue create [option] projectName 1.6 运行vue项目 vue serve 2. render函数 2.1 应用场景 当我们因为一些需求引入的并非是完整版的 vue 时，没有模板解析器，无法识别 template 项，导致页面无法展示，使用render函数可以将模板内容利用 createElement 函数创建并返回到页面中 import Vue from 'vue' Vue.config.productionTip = false; new Vue({ el:'#app', render(createElement){ return createElement('h1','标题内容'); } }); render函数没有用到this，所以可以简写为箭头函数，又因为函数中只有一条返回语句，所以可以直接简写为如下格式： import Vue from 'vue' Vue.config.productionTip = false; new Vue({ el:'#app', render: h=&gt;h('h1','标题内容'); }); 2.2 总结 vue.js与vue.runtime.xxx.js的区别 vue.js 是完整版的 Vue，包含核心功能和模板解析器 vue.runtime.xxx.js 是运行版的 Vue，只包含核心功能没有模板解析器 vue.runtime.xxx.js 没有模板解析器，所以不能使用 template 配置，需要使用 render 函数接收到的createElement 函数去指定模板内容 3. 覆写脚手架配置 3.1 查看脚手架配置 vue inspect &gt; output.js 3.2 覆写配置 vue.config.js module.exports = { pages:{ index:{ //入口 entry:'src/yourJs.js' } }, //关闭语法检查 lintOnSave:false } npm run serve 3.3 具体配置项 详见：https://cli.vuejs.org/zh/ 4. ref属性 被用来给元素或子组件注册引用信息（id的替代者） 当我们为对应元素设置ref后，可以直接通过this调用$refs获取到对应名称的元素，如果为html元素则返回真实DOM对象，如果为组件元素则返回组件实例对象 &lt;template&gt; &lt;img alt=&quot;Vue logo&quot; src=&quot;./assets/logo.png&quot; ref=&quot;img&quot;&gt; &lt;HelloWorld msg=&quot;Welcome to Your Vue.js App&quot; ref=&quot;hw&quot;/&gt; &lt;School ref=&quot;school&quot;/&gt; &lt;button @click=&quot;showElement&quot; ref=&quot;btn&quot;&gt;点我输出元素对象&lt;/button&gt; &lt;/template&gt; &lt;script&gt; import HelloWorld from './components/HelloWorld.vue' import School from './components/School.vue'; export default { name: 'App', components: { HelloWorld,School }, methods:{ showElement(){ console.log(this.$refs.img); //真实DOM元素 console.log(this.$refs.btn); //真实DOM元素 console.log(this.$refs.school); //组件实例对象 console.log(this.$refs.hw); //组件实例对象 } } } &lt;/script&gt; &lt;style&gt; #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } &lt;/style&gt; 5. props配置 props属性：用于接收组件调用方使用组件时传入的数据 5.1 props简单接收 &lt;template&gt; &lt;!--组件结构--&gt; &lt;div class=&quot;school&quot;&gt; &lt;h1&gt;{{ msg }}&lt;/h1&gt; &lt;h2&gt;学校名称：{{schoolName}}&lt;/h2&gt; &lt;h2&gt;学校地址：{{schoolAddress}}&lt;/h2&gt; &lt;h2&gt;学校面积：{{schoolArea*100}}&lt;/h2&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; //组件交互相关代码 export default { // eslint-disable-next-line vue/multi-word-component-names name: 'School', data(){ return { msg:&quot;SchoolComponentMsg&quot; } }, //简单接收 props:['schoolName','schoolAddress','schoolArea'] } &lt;/script&gt; &lt;style&gt; //组件样式 *{ padding:0; margin:0; } &lt;/style&gt; 5.2 props接收时限定传入的数据类型 &lt;template&gt; &lt;!--组件结构--&gt; &lt;div class=&quot;school&quot;&gt; &lt;h1&gt;{{ msg }}&lt;/h1&gt; &lt;h2&gt;学校名称：{{schoolName}}&lt;/h2&gt; &lt;h2&gt;学校地址：{{schoolAddress}}&lt;/h2&gt; &lt;h2&gt;学校面积：{{schoolArea*100}}&lt;/h2&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; //组件交互相关代码 export default { // eslint-disable-next-line vue/multi-word-component-names name: 'School', data(){ return { msg:&quot;SchoolComponentMsg&quot; } }, //接收的同时对数据类型进行限制 props:{ schoolName:String, schoolAddress:String, schoolArea:Number } } &lt;/script&gt; &lt;style&gt; //组件样式 *{ padding:0; margin:0; } &lt;/style&gt; 5.3 props接收时限定传入数据的各项属性 &lt;template&gt; &lt;!--组件结构--&gt; &lt;div class=&quot;school&quot;&gt; &lt;h1&gt;{{ msg }}&lt;/h1&gt; &lt;h2&gt;学校名称：{{schoolName}}&lt;/h2&gt; &lt;h2&gt;学校地址：{{schoolAddress}}&lt;/h2&gt; &lt;h2&gt;学校面积：{{schoolArea*100}}&lt;/h2&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; //组件交互相关代码 export default { // eslint-disable-next-line vue/multi-word-component-names name: 'School', data(){ return { msg:&quot;SchoolComponentMsg&quot; } }, props:{ schoolName:{ type: String, //数据类型 required: true //必须传入 //default:&quot;慕课网&quot; 默认值 }, schoolAddress:{ type:String, //数据类型 required: true //必须传入 //default: &quot;上海&quot; 默认值 }, schoolArea:{ type: Number, //数据类型 required: true //必须传入 //default: 200 默认值 } } } &lt;/script&gt; &lt;style&gt; //组件样式 *{ padding:0; margin:0; } &lt;/style&gt; 5.4 修改传入数据的解决方案 Vue 默认不推荐我们直接修改传入的数据，虽然我们可以修改成功，但是Vue会报错，因为这个过程可能导致Vue 产生错误，但是我们在业务开发中仍然有这样的需求，解决方案就是我们通过在组件定义对应的承接传入数据的data属性，并让页面绑定我们组件内部的data属性，日后修改时只需要修改我们组件内部拥有传入数据的data属性即可重新完成模板解析，实现我们想要的效果 6. mixin混入 mixin混入：一段配置让多个组件共用时，可以使用混入完成 6.1 书写需要混入的内容 export const mixin = { data(){ return { username:&quot;zhiyuan&quot;, email:&quot;zhiyuanworkemail@163.com&quot;, index:&quot;https://www.zync.top&quot; } } } 6.2 引入需要混入的内容 6.2.1 局部引入 &lt;template&gt; &lt;!--组件结构--&gt; &lt;div class=&quot;school&quot;&gt; &lt;h1&gt;{{ msg }}&lt;/h1&gt; &lt;h2&gt;学校名称：{{schoolName}}&lt;/h2&gt; &lt;h2&gt;学校地址：{{schoolAddress}}&lt;/h2&gt; &lt;h2&gt;学校面积：{{schoolArea*100}}&lt;/h2&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; import {mixin} from &quot;@/mixin&quot;; //组件交互相关代码 export default { name: 'School', data(){ return { msg:&quot;SchoolComponentMsg&quot; } }, mixins:[mixin] } &lt;/script&gt; &lt;style&gt; //组件样式 *{ padding:0; margin:0; } &lt;/style&gt; 6.2.2 全局引入 在main.js中作如下修改 import Vue from 'vue' import App from './App.vue' import {mixin} from &quot;@/mixin&quot;; Vue.mixin(mixin); new Vue({ el:&quot;#app&quot;, render: h=&gt;h(App) }) 6.3 注意事项 在混入进行的时候，如果组件内部已经有对应data中同名的属性，则会以组件自己的为准 7. 插件 插件本质上就是一个对象，但是Vue规定这个对象中必须存在一个install方法，用于增强Vue，我们可以在其中定义全局的过滤器，定义自己的指令，定义全局的混入等等 7.1 示例 export default{ install(){ console.info('自定义插件'); } } 7.2 引入插件 //引入自定义插件 import plugins from &quot;@/plugins&quot;; Vue.use(plugins); 7.3 进阶 export default{ install(Vue){ console.info('自定义插件',Vue); //全局过滤器 Vue.filter('filterName',function (methodParam){ return methodParam; }); //自定义指令 Vue.directive('fbind',{ bind(element,binding){ element.value = binding.value; }, inserted(element,binding){ element.focus(); }, updated(element,binding){ element.value = binding.value; } }); //定义混入 Vue.mixin({ data(){ return { username:&quot;zhiyuan&quot;, gender:&quot;male&quot; } } }) } } 8. scoped样式 当我们书写的vue文件最终被解析为浏览器能够识别的js、css、html时，其实我们所有的css会完成汇总，而汇总样式就会产生一个问题，那就是选择器名称重复时，某些样式可能被覆盖，覆盖的原则是由引入组件的顺序决定的，谁先被引入，谁的样式就会被覆盖，但是我们通常不希望产生这样的结果，所以vue为我们提供了一个解决方案，那就是使用scoped属性，来限定css的作用域或者说css有效的范围 &lt;style scoped&gt; //组件样式 *{ padding:0; margin:0; } &lt;/style&gt; 实现原理 Vue在汇总页面样式时，会在对应组件的容器上添加一个名为 data-v-xxxx 的属性，然后通过属性选择器来限定指定样式作用的容器 书写less 安装less-loader npm i less-loader@10.2.0 书写less &lt;style lang=&quot;less&quot; scoped&gt; &lt;/style&gt; 9. ToDoList案例 组件化的编码流程 拆分静态组件：组件按照功能点拆分，命名不要与html元素冲突 实现动态组件：考虑好数据的存放位置，确认数据是一个组件在用还是一些组件都在用 一个组件在用：放在组件自身即可 一些组件在用：放在它们共同的父组件上 实现交互：从绑定事件开始 props适用于 父组件与子组件进行通信 子组件与父组件进行通信（需要父组件传给子组件一个函数） 使用v-model是要切记：v-model绑定的值不能是props传过来的值，因为props是不建议修改的 props传过来的如果是对象类型的值，修改对象中的属性时Vue不会报错，但是不推荐这么做，因为这只是Vue没有检测到，并不意味着Vue允许我们这么做 10. 浏览器本地存储 10.1 LocalStorage //存储基本类型到localStorage中最终都会呈现为字符串类型 window.localStorage.setItem('key','value'); //存储对象需要将对象转为JSON window.localStorage.setItem('key',JSON.stringify(object)); //读取localStorage中存储的对象 JSON.parse(JSON.parse(localStorage.getItem('key')); //从localStorage中移除设置的信息 window.localStorage.removeItem('key'); //清空localStorage window.localStorage.clear(); 10.2 SessionStorage //存储基本类型到sessionStorage中最终都会呈现为字符串类型 window.sessionStorage.setItem('key','value'); //存储对象需要将对象转为JSON window.sessionStorage.setItem('key',JSON.stringify(object)); //读取sessionStorage中存储的对象 JSON.parse(JSON.parse(sessionStorage.getItem('key')); //从sessionStorage中移除设置的信息 window.sessionStorage.removeItem('key'); //清空sessionStorage window.sessionStorage.clear(); LocalStorage：浏览器关闭后依然存在 SessionStorage：一次会话结束后就不存在了，即关闭浏览器后就不存在了 11. 组件自定义事件 一种组件间通信的方式，适用于子组件与父组件通信 使用场景：A是父组件，B是子组件，B想给A传数据，那么就要在A中给B绑定自定义事件（事件的回调在A中） 绑定自定义事件 第一种方式，在父组件中 &lt;Demo @事件名=&quot;test&quot;/&gt; 或 &lt;Demo v-on:事件名=&quot;test&quot;&gt; 第二种方式，在父组件中 &lt;Demo ref=&quot;demo&quot;&gt; ...... mounted(){ this.$refs.xxx.$on('事件名',this.test); } 若想让自定义事件只能触发一次，可以使用 once 修饰符，或 $once 方法 触发自定义事件 this.$emit('事件名',数据) 解绑自定义事件 this.$off('事件名') 组件上绑定原生DOM事件，需要使用 native 修饰符 注意：通过 this.$refs.xxx.$on('事件名',回调) 绑定自定义事件时，回调要么配置在methods中，要么用箭头函数，否则this指向会出问题 12. 全局事件总线 任意组件间通信 new Vue({ el:&quot;#app&quot;, render:h =&gt; h(App), //vm创建之前 beforeCreate(){ Vue.prototype.$bus = this; //安装全局事件总线 } }); 一种组件间的通信方式，适用于任意组件间通信 安装全局事件总线 new Vue({ el:&quot;#app&quot;, render:h =&gt; h(App), beforeCreate(){ Vue.prototype.$bus = this; //安装全局事件总线,$bus就是当前应用的vm对象 } }); 使用事件总线 接收数据：A组件想接收数据，则在A组件汇总给$bus绑定自定义事件，事件的回调留在A组件自身 methods(){ demo(data){.......} } ...... mounted(){ this.$bus.$on('xxx',this.demo); } 提供数据：this.$bus.$emit('xxx',数据) 最好在 beforeDestory() 钩子函数中用 $off 去解绑当前组件所用到的事件 13. 消息订阅与发布 pubsub 仍然是一种支持任意组件通信的方式 安装 pubsub-js npm i pubsub-js 引入pubsub import pubsub from 'pubsub-js' 发布消息 //组件挂载完毕 mounted(){ //发布消息 pubsub.publish('消息名','消息内容'); } 订阅消息 //组件挂载完毕 mounted(){ //订阅消息 this.pubId = pubsub.subscribe('消息名',function('消息名','消息内容'){ console.log('有人发布了消息，消息名为：' + 消息名 + '@消息内容为：' + 消息内容); ..... }); } //上方写法中存在错误，错在当前this并非是vc对象，老生常谈的，如果我们需要使用第三方js库时，vue不会帮我们在管理对应的函数、对象以及其他信息，如果想要被vue管理到，就需要把普通函数写成箭头函数，这样this就会去父级找到vm或者vc，正确写法有如下两种： //第一种 methods:{ demo('消息名','消息内容'){ console.log('有人发布了消息，消息名为：' + 消息名 + '@消息内容为：' + 消息内容); ..... } } //组件挂载完毕 mounted(){ //订阅消息 this.pubId = pubsub.subscribe('消息名',this.demo); } //第二种 //组件挂载完毕 mounted(){ //订阅消息 this.pubId = pubsub.subscribe('消息名',('消息名','消息内容') =&gt; { console.log('有人发布了消息，消息名为：' + 消息名 + '@消息内容为：' + 消息内容); ..... }); } 取消订阅 beforeDestory(){ //根据订阅消息ID 取消订阅消息 pubsub.unscribe(pubId); } 14. $nextTick 由 this.$nextTick(回调函数) 指定的回调函数会在DOM节点更新之后执行 作用：在下一次DOM更新结束后执行其指定的回调 应用场景：当数据改变后，要基于 数据改变后 渲染的DOM进行某些操作时，可以使用此方法来指定 this.$nextTick(function(){ this.$refs.inputTile.focus(); }); 15. 过渡与动画 15.1 动画 CSS3 @keyframes 动画名称 { from { transform: translateX(-100px); } to { transform: translateX(0px); } } .come { /* animation:动画名称 持续时间 是否匀速 */ animation: 动画名称 1s linear; } .go{ animation: 动画名称 1s linear reverse; } Vue 使用 &lt;transition&gt;&lt;/transition&gt; 标签包裹需要展示动画的内容 @keyframes 动画名称 { from { transform: translateX(-100px); } to { transform: translateX(0px); } } .v-enter-active { animation: 动画名称 1s linear; } .v-leave-active { animation: 动画名称 1s linear reverse; } &lt;button @click=&quot;isShow = !isShow&quot;&gt;显示/隐藏&lt;/button&gt; &lt;transition&gt; &lt;h1 v-show=&quot;isShow&quot;&gt;内容&lt;/h1&gt; &lt;/transition&gt; appear属性 我们发现只有我们点击按钮才会触发动画效果，但是通常我们需要一个动画在页面展示时立即播放一次动画，name我们可以在 &lt;transition&gt;&lt;/transition&gt; 标签内添加 appear 属性 name属性 我们可以给动画起名字，来让一个组件内可以有多个动画 &lt;transition name=&quot;动画名称&quot;&gt;&lt;/transition&gt; 对应的css动画名称就需要改成如下格式 .动画名称-enter-active {} .动画名称-leave-active {} 其实我们默认不写，就是将动画名称默认为了 v 15.2 过渡 /* 进入的起点 */ .动画名称-enter { transform: translateX(-100%); } /* 进入的终点 */ .动画名称-enter-to { transform: translateX(0); } /* 离开的起点 */ .动画名称-leave { transform: translateX(0); } /* 离开的终点 */ .动画名称-leave-to { transform: translateX(-100%); } /* 进入的起点,离开的终点 */ .动画名称-enter,.动画名称-leave-to { transform: translateX(-100%); } /* 进入的终点，离开的起点 */ .动画名称-enter-to,.动画名称-leave{ transform: translateX(0); } .动画名称-enter-active,.动画名称-leave-active{ transition: 0.5s linear; } 15.3 多个元素的过渡 &lt;transition&gt;&lt;/transition&gt;只能够加在一个单独的元素上，如果需要加在多个元素上需要使用&lt;transition-group&gt;&lt;/transition-group&gt; 而且在使用 transition-group标签时，必须为其包裹的每一个元素设置一个key &lt;transition-group name=&quot;动画名称&quot; appear&gt; &lt;h1 v-show=&quot;isShow&quot; key=&quot;1&quot;&gt;&lt;/h1&gt; &lt;h1 v-show=&quot;isShow&quot; key=&quot;2&quot;&gt;&lt;/h1&gt; &lt;/transition-group&gt; 15.4 第三方动画 安装 animate.css npm install animate.css 引入 animate.css import 'animate.css' 使用方式 &lt;transition-group name=&quot;animate__animated animate__bounce&quot; enter-active-class=&quot;animate__swing&quot; leave-active-class=&quot;animate__backOutUp&quot; appear&gt; &lt;/transition-group&gt; 16. 配置代理 axios 安装 axios npm install axios 引入axios import axios from 'axios' 使用axios export default{ name: 'App', methods:{ getUserInfo(){ axios.get('http://localhost:8080/cloud-drive/user/10000010').then( response =&gt; { console.log(&quot;请求成功&quot;,response.data); }, error =&gt; { console.log(&quot;请求失败&quot;,error.message); } ); } } } 解决跨域问题 cors：后端放行跨域访问 json：利用了script标签的src属性在引入外部文件时不受同源策略影响的特性 代理服务器 nginx vue-cli 16.1 方式一 修改 vue.config.js 文件 module.exports = { devServer:{ proxy: 'http://localhost:8080' } } 这个方法只能是请求地址没有的资源才走代理服务器，并不能解决我们的跨域问题 16.2 方式二 module.exports = { devServer:{ proxy: { //匹配所有以/api开头的请求路径 '/cloud-drive':{ target:'http://localhost:8080', pathRewrite:{'^/cloud-drive':''} // key为书写的路径匹配正则，value为与匹配替换的路径 ws: true, // 开启webscoket支持 changeOrigin: true // 代理功能实现的核心属性，前端解决跨域问题 }, //匹配所有以/foo开头的请求路径 '/admin':{ target: 'http://localhost:8085' } } } } 17. vue-resource 安装 vue-resource npm install vue-resource 引入 vue-resource //main.js 引入插件 import vueResource from 'vue-resource' Vue.use(vueResource); 官方已经不再维护，不建议使用 18. 插槽 作用：让父组件可以向子组件指定位置插入html结构 应用场景：当一个组件被使用多次，但是组件中部分需求有所不同时，可以使用vue的插槽功能，让不同的需求都可以有对应的实现 18.1 默认插槽 &lt;template&gt; &lt;h1&gt;demo组件&lt;/h1&gt; &lt;slot&gt;默认插槽&lt;/slot&gt; &lt;/template&gt; &lt;demo&gt; &lt;h2&gt;使用默认插槽&lt;/h2&gt; &lt;/demo&gt; 18.2 具名插槽 &lt;template&gt; &lt;h1&gt;demo组件&lt;/h1&gt; &lt;slot name=&quot;center&quot;&gt;我是具名插槽center&lt;/slot&gt; &lt;slot name=&quot;footer&quot;&gt;我是具名插槽footer&lt;/slot&gt; &lt;/template&gt; &lt;demo&gt; &lt;h2 slot=&quot;center&quot;&gt;使用具名插槽center&lt;/h2&gt; &lt;h2 slot=&quot;footer&quot;&gt;使用具名插槽footer&lt;/h2&gt; &lt;/demo&gt; &lt;demo&gt; &lt;template slot=&quot;center&quot;&gt; &lt;h2&gt;使用具名插槽center&lt;/h2&gt; &lt;/template&gt; &lt;template v-slot:footer&gt; &lt;h2&gt;使用具名插槽footer&lt;/h2&gt; &lt;/template&gt; &lt;/demo&gt; 18.3 作用域插槽 数据在组件的自身，但是根据数据生成的结构需要组件的使用者来决定 &lt;template&gt; &lt;h1&gt;demo组件&lt;/h1&gt; &lt;slot :userList=&quot;userList&quot;&gt;&lt;/slot&gt; &lt;/template&gt; &lt;template&gt; &lt;demo&gt; &lt;template scope=&quot;userList&quot;&gt; &lt;ul&gt; &lt;li v-for=&quot;(user,index) in userList&quot; :key=&quot;user.id&quot;&gt;{{user.name}}&lt;/li&gt; &lt;/ul&gt; &lt;/template&gt; &lt;/demo&gt; &lt;demo&gt; &lt;template slot-scope=&quot;userList&quot;&gt; &lt;h2&gt;{{userList[0].name}}&lt;/h2&gt; &lt;/template&gt; &lt;/demo&gt; &lt;/template&gt; 19. vuex 作用：专门在Vue中实现集中式状态数据管理的一个Vue插件，对Vue应用中多个组件的共享状态进行集中式的管理，也是一种组件间通信方式，且适用于任意组件间通信 地址：https://github.com/vuejs/vuex 应用场景： 多个组件依赖于同一状态 来自不同的组件的行为需要变更为同一状态 19.1 vuex工作原理图 19.2 快速入门 安装 vuex 在2022年2月7日，vue3 成为了默认版本，使用 npm install vue 如果不指定版本号，那么就会直接安装 vue3，并且使用 npm install vuex 不指定版本号，也会直接安装vuex4版本，vue3.0 对应 vuex4.0 npm install vuex@3 引入 vuex import Vue from &quot;vue&quot; import Vuex from &quot;vuex&quot; Vue.use(Vuex); 创建src/store目录，并新建index.js文件 //引入Vue import Vue form 'vue' //引入Vuex import Vuex from 'vuex' Vue.use(Vuex); //用于响应组件中的动作 const actions = { } //用于操作数据state const mutations = { } //用于存储数据 const state = { } //创建store const store = new Vuex.Store({ actions:actions, mutations:mutations, state:state }); //暴露store export default store; 引入 store ，修改 main.js 文件如下 import store from './store' 19.3 getter配置项 const getters = { ...... } const store = new Vuex.Store({ ...... getters:getters }); 19.4 mapState与mapGetters 引入 mapState mapGetters import {mapState,mapGetters} from 'vuex' 使用 mapState mapGetters computed:{ //借助mapState生成计算属性，从state中读取数据 ...mapState({sum:'sum',school:'school',subject:'subject'}), ...mapState(['sum','school','subject']) //简写形式 //借助mapGetters生成计算属性，从getters中读取数据 ...mapGetters({sum:'sum',school:'school',subject:'subject'}) ...mapGetters(['sum','school','subject']) } 19.5 mapActions与mapMutations 引入 mapMutations mapActions import {mapMutations,mapActions} from 'vuex' 使用 mapMutations mapActions methods:{ //借助mapMutations生成对应的方法，方法中会调用commit去联系mutations中的方法 ...mapMutations({increment:'SUM',decrement:'SUB'}) ...mapMutations(['SUM','SUB']); //简写形式 //借助mapActions生成对应的方法，方法中会调用dispatch去联系actions中 ...mapActions({increment:'sum',decrement:'sub'}) ...mapActions(['sum','sub']) } 19.6 多组件共享数据 19.7 vuex的模块化编码 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); const options1 = { namespaced:true, //开启命名空间 action:{}, mutation:{}, state:{}, getters:{} } const options2 = { namespaced:true, //开启命名空间 action:{}, mutation:{}, state:{}, getters:{} } export default new Vuex.Store({ modules:{ a:options1, b:options2 } }); ...mapState('a',['sum','school','subject']); ...mapState('b',['persionList']) this.store.state.personAbout.list; ...mapMutations('a',{increment:'SUM',decrement:'SUB'}) this.$store.commit('a/SUM',2); ...mapActions('a',{increment:'sum',decrement:'sub'}) this.$store.dispatch('a/sum',2); ...mapGetters('a',['sum','school','subject']) this.$store.getters['a/sum']; 20. 路由 SPA（Single Page Web Application）：单页面应用 2022年2月7日以后，vue-router的默认版本变为了4，所以如果我们不需要最新版本，就要在安装时指定他的版本 安装 vue-router npm install vue-router 引入 vue-router import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter); new Vue({ el: '#app', render: h =&gt; h(App), router:'' }) 创建src/router目录，并新建index.js文件 import VueRouter from 'vue-router' import About from '../components/About' import Home from '../component/Home' //创建并暴露一个路由器 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }); 在页面中使用 &lt;router-link&gt; 标签的路由切换 代替 &lt;a&gt; 标签的页面跳转 &lt;router-link to=&quot;/about&quot; active-class=&quot;active&quot;&gt;About&lt;/router-link&gt; &lt;router-link to=&quot;/home&quot; active-class=&quot;active&quot;&gt;Home&lt;/router-link&gt; 在页面中使用 &lt;router-view&gt; 标签指定路由切换对应组件呈现的位置 &lt;router-view&gt;&lt;/router-view&gt; 20.1 注意事项 路由组件一般放在 pages 文件夹，普通组件一般放在 components 中 通过切换，隐藏了的路由组件，默认是被销毁掉的，需要的时候才会再次挂载 每个组件都有自己的 $route 属性，里面存储着自己的路由信息 整个应用只有一个 router，可以通过组件的 $router 属性获取到 20.2 嵌套路由 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { path:'message', component:Message } ] } ] }); 20.3 路由的query参数 传递参数 &lt;!--to的字符串写法--&gt; &lt;router-link :to=&quot;/home/message/detail?id=666&amp;title=test&quot;&gt;跳转&lt;/router-link&gt; &lt;!--to的对象写法--&gt; &lt;router-link :to=&quot;{ path:'/home/message/detail', query:{ id:666, title:'test' } }&quot;&gt;跳转 &lt;/router-link&gt; 接收参数 $route.query.id $route.query.title 20.4 命令路由 作用：简化路由的跳转 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { name:'message' path:'message', component:Message } ] } ] }); &lt;router-link :to=&quot;{name:'message'}&quot;&gt;跳转&lt;/router-link&gt; &lt;router-link :to=&quot;{ name:'message', query:{ id:666, title:'test' } }&quot;&gt;跳转 &lt;/router-link&gt; 20.4 路由的params参数 &lt;router-link :to=&quot;{name:'message'}&quot;&gt;跳转&lt;/router-link&gt; &lt;router-link :to=&quot;{ name:'message', params:{ id:666, title:'test' } }&quot;&gt;跳转 &lt;/router-link&gt; 当通过params传递参数时，只能用name，不能用path 20.5 路由的props配置 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { name:'message' path:'message', component:Message, children:[ { name:'detail', path:'detail/:id/:title', component:Detail, //第一种写法，值为对象，缺点：数据写死，一般不用 props:{ id:'666', title:'test' } //第二种写法，值为boolean，true时会把该路由组件收到的所有params参数以props形式传给Detail组件 props:true //第三种写法，值为函数 props(){ return {id:'666',title:'test'} } } ] } ] } ] }); props($route){ return { id:$route.query.id, title:$route.query.title } } 20.6 router-link的replace属性 作用：控制路由跳转时操作浏览器历史记录的模式 浏览器的历史记录由两种写入方式，分别为 push 和 replace，push是追加历史记录，replace是替换当前记录，路由跳转默认为push 我们只需要在 &lt;router-link replace&gt;标签中添加replace属性，就可以实现浏览器没有历史记录即无法后退的场景 20.7 编程式路由导航 作用：不用借助 router-link 标签实现路由跳转，让路由跳转更加灵活 代码 this.$router.push({ name:'detail', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'detail', params:{ id:xxx, title:xxx } }) //前进 this.$router.forward(); //后退 this.$router.back(); //前进(&gt;0)/后退(&lt;0) this.$router.go(3); 20.8 缓存路由组件 作用：让不展示的路由组件保持挂载，不被销毁 代码 &lt;!--缓存单个路由组件--&gt; &lt;keep-alive include=&quot;News&quot;&gt; &lt;router-view&gt;&lt;/router-view&gt; &lt;/keep-alive&gt; &lt;!--缓存多个路由组件--&gt; &lt;keep-alive :include=&quot;['News','Message']&quot;&gt; &lt;router-view&gt;&lt;/router-view&gt; &lt;/keep-alive&gt; 20.9 两个新的生命周期钩子 //激活 actived(){ ....... }, //失活 deactived(){ ....... } 20.10 路由守卫 作用：对路由进行权限控制 分类：全局守卫、独享守卫、组件内守卫 全局前置守卫：初始化时执行，每次路由切换前执行 const router = new VueRouter({options}); router.beforeEach((to,from,next)=&gt;{ if(to.meta.isAuth){ if(localStorage.getItem('role') === 'admin'){ next(); }else{ alert('无权查看'); } }else{ next(); } }); 全局后置守卫：初始化时执行，每次路由切换后执行 const router = new VueRouter({options}); router.afterEach((to,from)=&gt;{ if(to.meta.title){ document.title = to.meta.title; }else{ document.title = '主页'; } }); 独享路由守卫 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { name:'news', path:'news', component:News meta:{title:'新闻'}, beforeEnter:(to,from,next)=&gt;{ if(to.meta.isAuth){ if(localStorage.getItem('role') === 'admin'){ next(); }else{ alert('无权查看'); } }else{ next(); } } }, { name:'message' path:'message', component:Message } ] } ] }); 组件内路由守卫 //通过路由规则，进入该组件时被调用 beforeRouteEnter(to,from.next){ }, //通过路由规则，离开该组件时被调用 beforeRouteLeave(to,from,next){ } 20.11 history模式与hash模式 对于一个url来说，在 # 后面的内容就是hash值 hash值不会包含在http请求中 两种模式 hash模式 地址中永远带着 # 号，不美观 地址通过第三方分享，若第三方校验严格，地址会被标记为不合法 兼容性好 history模式 地址干净，美观 兼容性相较hash模式较差 应用部署上线时，需要后端人员支持，解决刷新页面服务端404的问题 六、Element-UI 地址：https://element.eleme.cn/#/zh-CN 七、Vue3 1. 使用 vue-cli 创建工程 2. 使用vite创建工程 3. 分析工程结构 3.1 分析入口文件main.js //引入的不再是Vue构造函数了，引入的是一个名为createApp的工厂函数 import {createApp} from 'vue' import App from './App.vue' //创建应用实例对象（类似于vue2的vm，但是比vm更加轻量） const app = createApp(App); app.mount(&quot;#app&quot;); 3.2 分析App.vue &lt;template&gt; &lt;!--vue3组件中的模板结构可以没有根标签--&gt; &lt;img alt=&quot;Vue logo&quot; src=&quot;./assets/logo.png&quot; ref=&quot;img&quot;&gt; &lt;HelloWorld msg=&quot;Welcome to Your Vue.js App&quot; ref=&quot;hw&quot;/&gt; &lt;School ref=&quot;school&quot; school-name=&quot;黑马程序猿&quot; school-address=&quot;北京&quot; v-bind:school-area=&quot;10&quot;/&gt; &lt;button @click=&quot;showElement&quot; ref=&quot;btn&quot;&gt;点我输出元素对象&lt;/button&gt; &lt;/template&gt; 4. 安装开发者工具 5. 初识setup Vue3中一个新的配置项，值为一个函数 组件中用到的数据data，方法methods等，均要配置到setup中 setup函数有两种返回值 返回一个对象，则对象中的属性、方法。在模板中均可以直接使用 若返回一个渲染函数，则可以自定义渲染内容 注意事项 尽量不要与vue2配置混用 vue2配置（data、methods、computed、watch...）中可以访问到setup中的属性、方法 但是setup中不能访问vue2的配置（data、methods、computed、watch...） 如果有重名，以setup优先 setup不能是一个async函数，因为返回值不再是return的对象，而是promise，模板看不到return对象中的属性 后期可以返回一个Promise实例，但是需要Suspense和异步组件的配合 6. ref函数 作用：定义一个响应式数据 语法：const xxx = ref(initValue) 创建一个包含响应式数据的引用对象 js中操作数据 xxx.value 模板中读取数据不需要 .value，直接 {{xxx}} 备注 接收的数据可以是基本类型，也可以是对象类型 基本类型的数据，响应式依然是靠 Object.defineProperty() 的 get 与 set 完成的 对象类型的数据，内部求助了Vue3的一个新函数 reactive 函数 import {ref} from 'vue' 7. reactive函数 作用：定义一个对象类型的响应式数据（基本类型别用它，用 ref 函数） 语法：const 代理对象 = reactive(被代理对象) 接收一个对象（或数组），返回一个代理器对象（Proxy对象） reactive定义的响应式数据是深层次的 内部基于ES6的Proxy实现，通过代理对象操作源对象内部数据都是响应式的 import {reactive} from 'vue' 8. vue3响应式原理 8.1 vue2响应式原理 实现原理 对象类型：通过 Object.defineProperty() 对属性的读取、修改进行拦截（数据劫持） 数组类型：通过重写更新数组的一系列方法来实现拦截 Object.defineProperty(data,'count',{ get(){}, set(){} }); 存在的问题 新增属性、删除属性，界面不会更新 直接通过数组下标修改数组内容，界面不会更新 8.2 vue3响应式原理 实现原理 通过Proxy（代理）：拦截对象中任意属性的变化，包括（属性值读写、属性添加、属性删除等） 通过Reflect（反射）：对被代理对象的属性进行操作 MDN文档中描述的Proxy与Reflect Proxy：https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy Reflect：https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect Proxy new Proxy(data,{ //有人读取data的某个属性时调用 get(target,attributeName){ return target[attributeName]; }, //有人修改data属性或者给data追加属性时调用 set(target,attributeName,value){ target[attributeName] = value; }, //有人删除data上某个属性时调用 deleteProperty(target,attributeName){ return delete target[attributeName]; } }); Reflect ECMA组织正在尝试将Object上的属性方法移植到Reflect身上 9. reactive与ref对比 从定义数据角度对比 ref 用来定义基本数据类型 reactive 用来定义对象或数组数据类型 ref 也可以定义对象或数组类型，但是最终内部会通过reactive转为代理对象 从原理的角度对比 ref 通过 Object.defineProperty() 的 get 和 set 来实现响应式 reactive 通过使用 Proxy 来实现响应式，并通过 Reflect 操作源对象内部的数据 从使用角度对比 ref 定义的数据，操作数据需要 .value，模板读取时不需要 .value reactive 定义的数据，操作数据与读取数据都不需要 .value 10. setup的两个注意点 setup的执行时机 在beforeCreate之前执行一次，this是undefined setup的参数 props：值为对象，存储组件外部传递过来并且组件内部声明接收了的属性 context：上下文对象 attrs：值为对象，存储组件外部传递过来，但是没有在props中声明接收的属性。相当于 this.$attrs slots：收到的插槽内容。相当于 this.$slots emit：分发自定义事件的函数。相当于 this.$emit props:['','',''...], emits:['','',''...], setup(props,context){ ... return { ... } } 11. computed计算属性 import {computed,reactive} from 'vue' setup(){ let person = reactive({ firstName:'张', lastName:'三' }); //简写形式，只考虑读取数据 person.fullName = computed(()=&gt;{ return person.firstName + person.lastName; }); //完整形式，考虑读写 person.fullName = computed({ get(){ return person.firstName + person.lastName; }, set(value){ const nameArr = value.split('-'); person.firstName = nameArr[0]; person.lastName = nameArr[1] ; } }); return { person } } 与Vue2中的computed配置功能一致，只是需要写在setup函数中 12. watch监视属性 与Vue2中的watch配置功能一致 两个注意事项 监视reactive定义的响应数据时，oldValue无法正确获取，强制开启了深度监视，deep属性失效 监视reactive定义的响应数据中某个属性时，deep属性有效 import {ref,reactive,watch} from 'vue' setup(){ let sum = ref(0); let msg = ref('hello'); let person = reactive({ name:'张三', age:18, job:{ name:'JavaEngineer', salary:20 } }); //监视ref所定义的一个响应式数据 watch(sum,(newValue,oldValue)=&gt;{ console.log('sum改变了',newValue,oldValue); }); //监视ref所定义的多个响应式数据 watch([sum,msg],(newValue,oldValue)=&gt;{ console.log('sum改变了',newValue,oldValue); },{immediate:true}); //监视reactive所定义的一个响应式数据，此处oldValue无法正常获取，deep属性失效 watch(person,(newValue,oldValue)=&gt;{ console.log('person改变了',newValue,oldValue); },{deep:false}); //监视reactive所定义的一个响应式数据的某个普通属性，此处oldValue可以正常获取，deep属性有效 watch(()=&gt;person.age,(newValue,oldValue)=&gt;{ console.log('person.age改变了',newValue,oldValue); }); //监视reactive所定义的一个响应式数据的某些普通属性 watch([()=&gt;person.age,()=&gt;person.name],(newValue,oldValue)=&gt;{ console.log('person.age或name改变了',newValue,oldValue); }); //监视reaction所定义的一个响应式数据中的对象属性，deep属性有效 watch(()=&gt;person.job,(newValue,oldValue)=&gt;{ console.log('person.job改变了',newValue,oldValue); },{deep:true}); return { sum,age,person } } watchEffect函数 watch的套路是：既要指明监视的属性，也要指明监视的回调 watchEffect的套路是：不用指明监视哪个属性，监视的回调中用到哪个属性，就监视哪个属性 watchEffect有点类似与computed 但是computed注重的是计算出来的值（回调函数的返回值），所以必须需要写返回值 而watchEffect更注重的是过程（回调函数的函数体），所以不用写返回值 watchEffect(()=&gt;{ const x1 = sum.value; const v2 = person.value; console('watchEffect配置的回调执行了'); }); 13. Vue3生命周期 Vue3中可以继续使用Vue2中的生命周期钩子，但是有两个被更名 beforeDestory 改名为 beforeUnmount destoryed 改名为 unmounted Vue3也提供了CompositionAPI形式的生命周期钩子函数，与Vue2对应关系如下： beforeCreate ===&gt; setup() created ===&gt; setup() beforeMount ===&gt; onBeforeMount() mounted ===&gt; onMounted() beforeUpdate ===&gt; onBeforeUpdate() updated ===&gt; onUpdated() beforeUnmount ===&gt; onBeforeUnmount() unmounted ===&gt; onUnmounted() setup(){ onBeforeMount(()=&gt;{ }); onMounted(()=&gt;{ }); onBeforeUpdate(()=&gt;{ }); onUpdated(()=&gt;{ }); onBeforeUnmount(()=&gt;{ }); onUnmounted(()=&gt;{ }); } 14. 自定义hook Vue中的hook：本质是一个函数，把setup函数中使用的CompositionAPI进行了封装 类似于Vue2中的mixin 自定义hook的优势：复用代码，让setup中的逻辑更清楚易懂 15. toRef与toRefs 作用：创建一个 ref 对象，其value值指向另一个对象中的某个属性 语法：const name = toRef(person,'name') 应用场景：需要将响应式对象中的某个属性单独提供给外部使用时 扩展：toRefs() 与 toRefs() 的功能一致，但可以批量创建多个ref对象，语法为：toRefs(person) return { person, ...toRefs(person) } 16. CompositionAPI 16.1 shallowReactive与shallowRef shallowReactive：只处理对象最外层属性的响应式（浅响应式） shallowRef：只处理基本数据类型的响应式，不进行对象的响应式处理 应用场景： 如果有一个对象数据，结构比较深，但变化时只是外层属性变化，可以使用shallowReactive 如果有一个对象数据，后续功能不会修改该对象中的属性，而是生成新的对象来替换，可以使用shallowRef 16.2 readonly与shallowReadonly readonly：让一个响应式数据变为只读（深只读） shallowReadonly：让一个响应式数据变为只读（浅只读） 应用场景：不希望数据被修改 16.3 toRaw与markRaw toRaw 作用：将一个由 reactive 生成的响应式对象转为普通对象 应用场景：用于读取响应式对象对应的普通对象，对这个普通对象的所有操作，不会引起页面的更新 markRaw 作用：标记一个对象，使其永远不会再成为响应式对象 应用场景： 有些值不应被设置为响应式，例如复杂的第三方类库等 当渲染具有不可变数据源的大列表时，跳过响应式转换可以提高性能 16.4 customRef 作用：创建一个自定义的 ref ，并对其依赖项跟踪和更新触发进行显式控制 实现防抖效果 &lt;template&gt; &lt;p&gt; This text only updates 1 second after you've stopped typing: &lt;/p&gt; &lt;p&gt;{{ text }}&lt;/p&gt; &lt;input v-model=&quot;text&quot; /&gt; &lt;/template&gt; &lt;script setup&gt; import { useDebouncedRef } from './debouncedRef.js' const text = useDebouncedRef('hello', 1000) &lt;/script&gt; import { customRef } from 'vue' export function useDebouncedRef(value, delay = 200) { let timeout return customRef((track, trigger) =&gt; { return { get() { track()//通知vue追踪value的变化 return value }, set(newValue) { clearTimeout(timeout) timeout = setTimeout(() =&gt; { value = newValue trigger()//通知vue重新解析模板 }, delay) } } }) } 16.5 provide与inject 作用：实现祖孙组件间通信 套路：父组件有一个 provide 选项来提供数据，子组件有一个 inject 选项来开始使用这些数据 具体写法 祖组件 setup(){ ...... let car = reactive({ name:'奔驰', price:'40万' }); provide('car',car) ...... } 后代组件 setup(){ ...... const car = inject('car'); return { car } ...... } 16.6 响应式数据的判断 isRef：检查一个值是否为一个ref对象 isReactive：检查一个对象是否是由 reactive 创建的响应式代理对象 isReadonly：检查一个对象是否是由 readonly 创建的只读代理对象 isProxy：检查一个对象是否是由 reactive 或者 readonly 方法创建的代理对象 17. CompositionAPI优势 17.1 OptionAPI 17.2 CompositionAPI 18. Fragment组件 在Vue2中，组件必须有一个根标签 在Vue3中，组件可以没有根标签，内部会将多个标签包含在一个Fragment虚拟元素中 好处：减少标签层级，减少内存占用 19. Teleport组件 Teleport 是一种能够将我们组件html结构移动到指定位置的技术 &lt;teleport to=&quot;移动位置&quot;&gt; &lt;div&gt;我是一个弹窗&lt;/div&gt; &lt;/teleport&gt; 20. Suspense组件 等待异步组件时渲染一些后备内容，获得更好的用户体验 使用步骤 异步引入组件 import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=&gt;import('./components/Child.vue')) 使用 Suspense 包裹组件，并配置好 default fallback &lt;template&gt; &lt;div class=&quot;#app&quot;&gt; &lt;h3&gt;我是App组件&lt;/h3&gt; &lt;Suspense&gt; &lt;template v-slot:default&gt; &lt;Child/&gt; &lt;/template&gt; &lt;template v-slot:fallback&gt; &lt;h3&gt;加载中......&lt;/h3&gt; &lt;/template&gt; &lt;/Suspense&gt; &lt;/div&gt; &lt;/template&gt; 21. 收尾 21.1 全局API的转移 Vue2中有许多全局API和配置 例如：注册全局组件，注册全局指令等 Vue3中对这些API做出了调整 将全局的API，即 Vue.xxx 调整到应用实例 app 上 Vue2 Vue3 Vue.config.xxx app.config.xxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties Vue.config.keyCodes 移除 21.2 其他改变 data选项应该始终被声明为一个函数 过渡类名的更改 Vue2.x写法 .v-enter,.v-leave-to{ opacity: 0; } .v-leave,.v-enter-to{ opacity: 1; } Vue3.x写法 .v-enter-from,v-leave-to{ opacity:0; } .v-leave-from,v-enter-to{ opacity:1; } 移除 keyCode 作为 v-on 的修饰符，同时也不再支持 config.keyCodes 移除 v-on.native 修饰符 父组件中绑定事件 &lt;my-component v-on:close=&quot;handleComponentEvent&quot; v-on:click=&quot;handleNativeClickEvent&quot;&gt;&lt;/my-component&gt; 子组件中声明自定义事件 &lt;script&gt; export default{ emits:['close'] } &lt;/script&gt; 移除过滤器 filter 过滤器虽然看起来很方便，但是它需要一个自定义语法，打破大括号内表达式只是javascript的假设，这不仅有学习成本，而且有实现成本，建议用方法调用或计算属性去替换过滤器 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/Vr0OXGyK8/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/Vr0OXGyK8/"},{"title":"微服务学习","content":"微服务 微服务就是一种经过良好架构设计的分布式架构方案，微服务架构的特征： 单一职责：微服务拆分力度更大，每一个服务都对应唯一的业务能力，做到单一职责，避免重复的业务开发 面向服务：微服务只对外暴露业务接口 自治：团队独立、技术独立、数据独立、部署独立 隔离性强：服务调用做好隔离、容错、降级，避免出现级联问题 第一天 一、认识微服务 1. 几种技术的比较 Dubbo SpringCloud SpringCloudAlibaba 注册中心 zookeeper、redis Eureka、Consul Nacos、Eureka 服务远程调用 Dubbo协议 Feign（http协议） Dubbo、Feign 配置中心 无 SpringCloudConfig SpringCloudConfig、Nacos 服务网关 无 SpringCloudGateway、Zuul SpringCloudGateway、Zuul 服务监控和保护 dubbo-admin，功能弱 Hystrix Sentinel 2. SpringCloud SpringCloud是目前国内使用最广泛的微服务框架 官网地址：https://spring.io/projects/spring-cloud 版本对应信息查询地址：https://start.spring.io/actuator/info SpringCloud集成了各种微服务功能组件，并给予SpringBoot实现了这些组件的自动装配，从而提供了良好的开箱即用体验 SpringCloud与SpringBoot的版本兼容情况如下： SpringCloud SpringBoot 2023.0.x aka Leyton 3.2.x 2022.0.x aka Kilburn 3.0.x，3.1.x（Starting with 2022.0.3） 2021.0.x aka Jubilee 2.6.x，2.7.x（Starting with 2021.0.3） 2020.0.x aka Ilford 2.4.x，2.5.x（Starting with 2020.0.3） Hoxton 2.2.x，2.3.x（Starting with SR5） Greenwich 2.1.x Finchley 2.0.x Edgware 1.5.x Dalston 1.5.x 3. 服务拆分及远程调用 3.1 服务拆分注意事项 微服务要根据业务模块拆分，做到单一职责，不同的微服务中不要重复开发相同的业务 微服务可以将业务暴露为接口，供其他微服务使用 微服务数据独立，不要访问其他微服务的数据库 3.2 微服务的远程调用 基于RestTemplate发起的http请求实现远程调用 4. 提供者与消费者 服务提供者：在一次业务中，被其他微服务调用的服务 服务消费者：在一次业务中，调用其他微服务的服务 二、Eureka注册中心 Eureka的作用：将所有微服务集中注册，微服务请求时向注册中心发送消息即可获取服务列表 消费者如何获取服务提供者的具体信息？ 服务提供者启动时会向EurekaServer注册自己的信息 消费者根据服务名称向EurekaServer拉取提供者信息 如果有多个服务提供者，消费者该如何选择？ 服务消费者利用负载均衡算法，从服务列表中挑选一个 消费者如何感知服务提供者的健康状态？ 服务提供者会每隔30秒向EurekaServer注册中心发送心跳请求，报告健康状态 EurekaServer会根据健康状态更新服务列表，消费者就可以获取到最新的服务信息 1. 搭建EurekaServer 搭建EurekaServer服务步骤如下： 创建项目，引入spring-cloud-starter-netflix-eureka-server依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-server&lt;/artifactId&gt; &lt;/dependency&gt; 编写启动类，添加@EnableEurekaServer注解 添加application.yml文件，书写配置内容如下： server: port: 10086 spring: application: name: eurekaserver eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka 2. 注册微服务 步骤如下： 在user-service项目中引入spring-cloud-starter-netflix-eureka-client的依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt; &lt;/dependency&gt; 在application.yml文件添加如下配置： spring: application: name: userservice eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka/ 3. 服务发现 在order-service中完成服务发现 修改OrderService的代码，修改访问的url路径，用服务名代替IP和端口号 String url = &quot;http://uservice/user/&quot; + order.getUserId(); User user = restTemplate.getForObject(url,User.class); 在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解@LoadBalanced： @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } 三、Ribbon负载均衡 1. 负载均衡的流程 order-service发起请求http://userservice/user/1 LoadBalancerInterceptor负载均衡拦截器拦截请求，交给RibbonLoadBanlancerClient RibbonLoadBanlancerClient获得服务名称交给DynamicServerListLoadBalancer DynamicServerListLoadBalancer从IRule中选取负载均衡规则去决定选择使用的服务并将服务地址返还给RibbonLoadBalancerClient RibbonLoadBalancerClient将带有服务名称的url修改为指定服务真实地址的url并发送请求 2. IRule负载均衡的策略 内置负载均衡规则类 规则描述 RoundRobinRule 简单轮询服务列表来选择服务器，它是Ribbon默认的负载均衡规则 AvailabilityFilteringRule 对以下两种服务器进行过滤：1. 在默认情况下，如果服务器3次连接均失败，该服务器就会被设置为&quot;短路&quot;状态，此状态持续30秒，如果再次连接失败，短路持续的时间就会几何倍增2. 并发数过高的服务器，如果一个服务器的并发连接数过高，配置了AvailabilityFilteringRule规则的客户端也会将其忽略，并发连接数的上限，可以由客户端的&lt;clientName&gt;-&lt;clientConfigNameSpace&gt;-ActiveConnectionsLimit属性进行配置 WeightedResponseTimeRule 为每一个服务器赋予一个权重值，服务器响应时间越长，该服务器权重越小 ZoneAvoidanceRule 以区域内可用的服务器为基础进行服务器的选择，使用Zone对服务器进行分类，Zone可以理解为一个机房，然后对Zone内的多个服务做轮询 BestAvailableRule 忽略短路的服务器，并选择并发数较低的服务器 RandomRule 随机选择一个可用的服务器 RetryRule 重试机制的选择逻辑 3. 修改负载均衡规则的方式 3.1 代码方式 在order-service中的Order Application类中，定义一个新的IRule： @Bean public IRule randomRule(){ return new RandomRule(); } 3.2 配置文件方式 在order-service的application.yml文件中，添加新的配置也可以修改规则： userservice: ribbon: NFLoadBanlancerRuleClassName: com.netflix.loadbalancer.RandomRule 4. 饥饿加载 Ribbon默认采用懒加载，即第一次访问时才会创建LoadBalanceClient，请求时间会很长，而饥饿加载会在项目启动时创建，降低第一次访问时的耗时，通过下面的配置开启饥饿加载： ribbon eager-load: enabled: true clients: - userservice 四、Nacos注册中心 GitHub： https://github.com/alibaba/nacos 版本对应说明：地址 前往Github下载安装包，解压到指定位置即可，使用以下命令启动： startup.cmd -m standalone 1. Nacos搭建 步骤如下： 创建项目，引入spring-cloud-alibaba的依赖 &lt;dependency&gt; &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-alibaba-dependencies&lt;/artifactId&gt; &lt;version&gt;2.2.5.RELEASE&lt;/version&gt; &lt;type&gt;pom&lt;/type&gt; &lt;scope&gt;import&lt;/scope&gt; &lt;/dependency&gt; 2. 注册微服务 对应微服务引入spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` 修改该服务的application.yml文件，配置如下： spring: cloud: nacos: server-addr: localhost:8848 3. Nacos服务多级存储模型 一级是服务，二级是集群，三级是实例 3.1 服务集群属性 修改提供者application.yml，添加配置如下： spring: cloud: nacos: server-addr: localhost:8848 #配置nacos服务端地址 discovery: cluster-name: HZ #配置集群名称 修改消费者application.yml，添加配置如下： servicename: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule NacosRule负载均衡策略 优先选择同集群服务实例列表 本地集群找不到提供者，才去其他集群寻找，并且会警告 确定了可用实例列表后，再采用随机负载均衡挑选实例 注：从spring-cloud-alibaba2020.*版本开始，已经逐渐弃用netflix相关组件，例如ribbon，要想继续使用，应该回退版本到spring-cloud-alibaba2.*，还要注意对应的spring-boot和spring-cloud版本的兼容性 版本说明：Github版本说明 实例的权重控制 Nacos控制台可以设置实例的权重值，0-1之间 统计群内的多个实例，权重越高被访问的频率越高 权重设置为0则完全不会被访问 3.2 环境隔离 namespace Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西，用来做最外层隔离，未设置namespace的情况下默认为public 实例 在Nacos控制台创建命名空间dev，并复制namespace的UUID 在注册服务的application.yml中添加如下配置文件，即可将该服务分配到此命名空间下，此时若其他服务与它不属于同一namespace下，将无法访问此服务 namespace: 8f6f25b2-4758-40f7-8445-6c36ae9fb962 spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: 西安 namespace: 8f6f25b2-4758-40f7-8445-6c36ae9fb962 #dev环境 Nacos环境隔离 namespace用来做环境隔离 每个namespace都有唯一id，由Nacos控制台生成UUID 不同namespace下服务不可见 可用来分别控制开发环境，测试环境，生产环境下的服务之间不可随意调用 4. Nacos和Eureka的区别 4.1 临时实例和非临时实例 服务注册时，在启动配置文件中添加以下配置来设置当前实例的类别 spring: cloud: nacos: discovery: ephemeral: false #设置为非临时实例 ephemeral: 英 [ɪˈfemərəl] 美 [ɪˈfemərəl] adj. 短暂的；（主指植物）短生的，短命的 n. 只生存一天的事物；短生植物 4.2 区别与联系 共同点 都支持服务注册和服务拉取 都支持服务提供者心跳方式做健康检测 区别 Nacos支持服务端主动检测提供者状态 临时实例采用心跳模式 非临时实例采用主动监测模式 在Nacos中，临时实例心跳不正常会被剔除，非临时实例则不会被剔除 Nacos支持服务列表变更的消息推送模式，服务列表更新及时 Nacos集群默认采用AP方式，当集群中存在非临时实例时，采用CP模式；Eureka采用AP方式 第二天 五、 Nacos配置管理 1. 统一配置管理 因为bootstrap.yml的优先级高于application.yml，所以我们可以通过将Nacos服务信息配置到bootstrap.yml中来读取Nacos管理的配置文件，然后与本地配置文件进行合并，然后创建Spring容器，加载Bean启动程序 1.1 在指定服务的pom中引入Nacos配置管理客户端依赖 &lt;dependency&gt; &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-starter-alibaba-nacos-config&lt;/artifactId&gt; &lt;/dependency&gt; 1.2 在指定服务的resources目录下添加一个bootstrap.yml文件，这是一个引导文件，优先级高于application.yml spring: application: name: user-service #服务名称 profiles: active: dev #开发环境 cloud: nacos: server-addr: localhost:8848 #Nacos地址 config: file-extension: yaml #文件后缀 2. 配置热更新 2.1 方式一 通过@Value注解注入，结合@RefreshScope来刷新 2.2 方式二 通过@ConfigurationProperties注解创建对应配置类注入，自动刷新 2.3 注意事项 不是所有的配置都适合放到配置中心，维护起来比较麻烦 建议将一些关键参数，需要运行时调整的参数放到nacos配置中心，一般都是自定义配置 3. 多环境配置共享 微服务启动时会从Nacos中读取多个配置文件： [spring.application.name]-[spring.profiles.active].yaml [spring.application.name].yaml 我们发现无论是开发环境，测试环境，生产环境，[spring.application.name].yaml一定会被加载，因此我们可以将多环境共享的配置内容写入这个文件 3.1 注意事项 当一个环境共享配置文件的值在本地配置文件中也存在时，以环境共享配置文件的值为主 多种配置的优先级： [spring.application.name]-[spring.profiles.active].yaml &gt; [spring.application.name].yaml &gt; application.yaml 4. 搭建Nacos集群 4.1 创建集群数据库 create database if not exists nacos_config default character set utf8mb4 collate utf8mb4_general_ci; 4.2 创建集群数据表 CREATE TABLE `config_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(255) DEFAULT NULL, `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `c_desc` varchar(256) DEFAULT NULL, `c_use` varchar(64) DEFAULT NULL, `effect` varchar(64) DEFAULT NULL, `type` varchar(64) DEFAULT NULL, `c_schema` text, `encrypted_data_key` text NOT NULL COMMENT '秘钥', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info'; CREATE TABLE `config_info_aggr` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(255) NOT NULL COMMENT 'group_id', `datum_id` varchar(255) NOT NULL COMMENT 'datum_id', `content` longtext NOT NULL COMMENT '内容', `gmt_modified` datetime NOT NULL COMMENT '修改时间', `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段'; CREATE TABLE `config_info_beta` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL COMMENT 'content', `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `encrypted_data_key` text NOT NULL COMMENT '秘钥', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta'; CREATE TABLE `config_info_tag` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `tag_id` varchar(128) NOT NULL COMMENT 'tag_id', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag'; CREATE TABLE `config_tags_relation` ( `id` bigint(20) NOT NULL COMMENT 'id', `tag_name` varchar(128) NOT NULL COMMENT 'tag_name', `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `nid` bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`nid`), UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation'; CREATE TABLE `group_capacity` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID，空字符表示整个集群', `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额，0表示使用默认值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限，单位为字节，0表示使用默认值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数，，0表示使用默认值', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限，单位为字节，0表示使用默认值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表'; CREATE TABLE `his_config_info` ( `id` bigint(64) unsigned NOT NULL, `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `data_id` varchar(255) NOT NULL, `group_id` varchar(128) NOT NULL, `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL, `md5` varchar(32) DEFAULT NULL, `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `src_user` text, `src_ip` varchar(50) DEFAULT NULL, `op_type` char(10) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `encrypted_data_key` text NOT NULL COMMENT '秘钥', PRIMARY KEY (`nid`), KEY `idx_gmt_create` (`gmt_create`), KEY `idx_gmt_modified` (`gmt_modified`), KEY `idx_did` (`data_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造'; CREATE TABLE `tenant_capacity` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID', `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额，0表示使用默认值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限，单位为字节，0表示使用默认值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限，单位为字节，0表示使用默认值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表'; CREATE TABLE `tenant_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `kp` varchar(128) NOT NULL COMMENT 'kp', `tenant_id` varchar(128) default '' COMMENT 'tenant_id', `tenant_name` varchar(128) default '' COMMENT 'tenant_name', `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc', `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source', `gmt_create` bigint(20) NOT NULL COMMENT '创建时间', `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info'; CREATE TABLE `users` ( `username` varchar(50) NOT NULL PRIMARY KEY, `password` varchar(500) NOT NULL, `enabled` boolean NOT NULL ); CREATE TABLE `roles` ( `username` varchar(50) NOT NULL, `role` varchar(50) NOT NULL, UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE ); CREATE TABLE `permissions` ( `role` varchar(50) NOT NULL, `resource` varchar(255) NOT NULL, `action` varchar(8) NOT NULL, UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE ); 4.3 向表中插入数据 INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN'); 4.4 配置Nacos 1. 打开 \\Nacos安装目录\\conf\\ 找到cluster.conf.example文件，将后缀.example去掉，编辑cluster.conf文件，配置多个Nacos服务的地址及端口 127.0.0.1:8845 127.0.0.1:8846 127.0.0.1:8847 找到application.properties文件，编辑文件 将第33行取消注释，表示我们所使用的数据库为mysql #*************** Config Module Related Configurations ***************# ### If use MySQL as datasource: spring.datasource.platform=mysql 将36,39,40,41行取消注释，并修改为我们刚才建立的集群数据库信息 ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&amp;connectTimeout=1000&amp;socketTimeout=3000&amp;autoReconnect=true&amp;useUnicode=true&amp;useSSL=false&amp;serverTimezone=UTC db.user.0=root db.password.0=root 2. 复制Nacos文件夹并分别修改为Nacos1，Nacos2，Nacos3 编辑application.properties文件，修改对应端口 nacos1 server.port = 8840 nacos2 server.port = 8845 nacos3 server.port = 8847 分别启动三个nacos节点 startup.cmd 4.5 Nginx反向代理 upstream nacos-cluster { server 192.168.1.7:8840; server 192.168.1.7:8845; server 192.168.1.7:8847; } server { listen 80; server_name localhost; location /nacos { proxy_pass http://nacos-cluster; } } 如果访问localhost/nacos提示404，那么就是80端口号被占用，请更改端口号重启nginx 六、 基于Feign的远程调用 1. 添加依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/ artifactId&gt; &lt;/dependency&gt; 2. 代码书写 在服务启动类上添加@EnableFeignClients注解 @SpringBootApplication @EnableFeignClients public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } } 创建远程调用服务接口，格式如下 @FeignClient(&quot;user-service&quot;) public interface UserClients { @GetMapping(&quot;/user/info/{id}&quot;) Response getUserInfoById(@PathVariable(&quot;id&quot;) Long id); } Feign调用远程服务接口实例 public Response getOrderInfoById(Long id){ Response response = new Response(); OrderInfo orderInfo = orderMapper.selectById(id); if(orderInfo!=null){ OrderVO orderVO = new OrderVO(); BeanUtils.copyProperties(orderInfo,orderVO); orderVO.setPrice(Double.valueOf((orderInfo.getPrice()/100))); Response userInfoById = userClients.getUserInfoById(orderInfo.getUserId()); orderVO.setUserInfo(userInfoById.getData()); response.code(ResponseCode.SUCCESS).message(&quot;获取成功&quot;).count(1).data(orderVO); } return response; } 返回结果 { &quot;code&quot;: 200, &quot;message&quot;: &quot;获取成功&quot;, &quot;count&quot;: 1, &quot;data&quot;: { &quot;id&quot;: &quot;1543151273902505986&quot;, &quot;price&quot;: 3902.0, &quot;name&quot;: &quot;OculusQuest2&quot;, &quot;num&quot;: 2, &quot;userId&quot;: &quot;1543062156246155265&quot;, &quot;userInfo&quot;: { &quot;id&quot;: &quot;1543062156246155265&quot;, &quot;username&quot;: &quot;zhiyuan121&quot;, &quot;email&quot;: &quot;5168154488@gmail.com&quot;, &quot;introduction&quot;: &quot;个人简介&quot;, &quot;phoneNumber&quot;: &quot;97938192868&quot;, &quot;nickname&quot;: &quot;絷缘&quot;, &quot;status&quot;: &quot;正常&quot;, &quot;registerTime&quot;: &quot;2022-07-02&quot; }, &quot;createTime&quot;: &quot;2022-07-02&quot; } } 3. Feign的自定义配置 类型 作用 说明 feign.Logger.Level 日志级别 包含四种不同级别：NONE、BASIC、HEADERS、FULL feign.codec.Decoder 响应结果解析器 远程调用返回结果解析 feign.codec.Encoder 请求参数编码 将请求参数编码 feign.Contract 支持的注解格式 默认SpringMVC注解 feign.Retryer 失败重试机制 请求失败重试，默认没有，但是会使用Ribbon重试 3.1 日志配置方式 配置文件方式 全局生效 feign: client: config: default: #default表示全局配置 loggerLevel: FULL #日志级别 局部生效 feign: client: config: user-service: #对应服务名表示局部配置 loggerLevel: FULL #日志级别 Java代码方式 声明一个Bean public class FeignClientConfiguration{ @Bean public Logger.Level feignLogLevel(){ return Logger.Level.BASIC; } } 全局生效：使用加在启动类上的@EnableFeignClients上 @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class) 局部生效：使用加在指定服务上的@FeignClient上 @FeignClient(value = &quot;user-service&quot;,configuration = FeignClientsConfiguration.class) 3. Feign性能优化 3.1 Feign底层客户端的实现 URLConnection：默认实现，不支持连接池 Apache HttpClient：支持连接池 OKHttp：支持连接池 因此优化Feign的性能主要包括： 使用连接池代替默认的URLConnection 日志级别，最好用basic或none 3.2 为Feign添加HttpClient支持 引入依赖 &lt;dependency&gt; &lt;groupId&gt;io.github.openfeign&lt;/groupId&gt; &lt;artifactId&gt;feign-httpclient&lt;/artifactId&gt; &lt;/dependency&gt; 配置连接池 feign: client: config: default: loggerLevel: BASIC httpclient: enabled: true #开启httpclient支持 max-connections: 200 #最大连接数 max-connections-per-route: 50 #每个路径最大连接数 4. Feign的最佳实践 4.1 方式一(继承) 给消费者的FeignClient和提供者的Controller定义统一的父接口作为标准 4.2 方式二（抽取） 将FeignClient抽取为独立模块，并且把接口有关的Entity、默认的Feign配置都放到这个模块中，提供给所有消费者使用 步骤： 首先创建一个module，命名为feign-api，然后引入feign依赖 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中 在order-service中引入feign-api依赖 修改order-service中所有与上述三个组件有关的import部分，改成导入feign-api中的包 当定义的FeignClient不在SpringBootApplication的扫描包范围时，这些FeignClient无法使用，解决方式有两种： 指定FeignClient所在包 @EnableFeignClients(basePackages = &quot;com.zhiyuan.feign.clients&quot;) 指定FeignClient字节码 @EnableFeignClients(clients = {UserClient.class}) 抽取为feign-api后会导致日志配置失效，通过添加 logging.level.com.zhiyuan.clients:DEBUG 可以使日志配置生效 七、统一网关Gateway 1. 网关的功能 1.1 身份认证和权限校验 1.2 服务路由、负载均衡 1.3 请求限流 2. SpringCloud中网关的实现 2.1 Gateway 2.2 zuul Zuul是基于Servlet实现的，属于阻塞式编程，而SpringCloudGateway则是基于Spring5中提供的WebFlux，属于响应式编程的实现，具备更好的性能 3. 搭建网关 3.1 创建新的module，引入SpringCloudGateway的依赖和nacos的服务发现依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-starter-gateway&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt; &lt;artifactId&gt;spring-cloud-alibaba-nacos-discovery&lt;/artifactId&gt; &lt;/dependency&gt; 3.2 编写路由配置及nacos地址 server: port: 10010 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 gateway: routes: #网关路由配置 - id: user-service #路由ID，自定义唯一即可 uri: lb://user-service # 路由目标地址 predicates: #路由断言，即用来判断请求是否符合路由规则的配置 - Path=/user/** 3.3 路由断言工厂Route Predicate Factory 我们在配置文件中写的断言规则只是字符串，这些字符串会被Predicate Factory读取并处理，转变为路由判断的条件 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 名称 说明 示例 After 某个时间点后的请求 - After=2022-06-20T17:00:00.369+8:00[Asia/Shanghai] Before 某个时间点前的请求 - Before2022-06-20T17:00:00.369+8:00[Asia/Shanghai] Between 某两个时间点之间的请求 - Between=2022-06-20T17:00:00.369+8:00[Asia/Shanghai],2022-07-01T17:00:00.369+8:00[Asia/Shanghai] Cookie 请求必须包含某些Cookie - Cookie= Header 请求必须包含某些Header - Header= Host 请求必须访问某个host - Host=** Method 请求必须以指定方式发起 - Method=GET,POST Path 请求路径必须符合指定规则 - Path= Query 请求参数必须包含指定参数 - Query= RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24 Weight 权重处理 3.4 路由过滤器GatewayFilter 局部过滤器 过滤器工厂 作用 参数 AddRequestHeader 给原始请求添加Header Header名称，Header值 AddRequestParameter 给原始请求添加请求参数 参数名称，参数值 AddResponseHeader 给原始响应添加Header Header名称，Header值 DedupeResponseHeader 剔除响应头中的重复值 Header名称，去重策略 Hystrix 引入Hystrix的断路器保护 HystrixCommand名称 FallbackHeaders 给FallbackURL请求头中添加具体的异常信息 Header名称 PrefixPath 给原始请求路径添加前缀 前缀路径 PreserveHostHeader 给请求添加一个preserveHostHeader=true的属性，路由过滤器会检查该属性来决定是否要发送原始的Host 无 RequestRateLimiter 对请求限流，限流算法为令牌桶 keyResolver、rateLimiter、status Code、denyEmptyKey、emptyKeyStatus RedirectTo 将原始请求重定向到指定URL HTTP状态码，重定向URL RemoveHopByHopHeadersFilter 将原始请求中IETF组织规定的Header信息全部移除 默认启用，可以通过配置指定删除的Header RemoveRequestHeader 将原始请求中的某个Header移除 Header名称 RemoveResponseHeader 将原始响应中的某个Header移除 Header名称 RewritePath 重写原始请求路径 原始路径正则表达式，重写后路径的正则表达式 RewriteResponseHeader 重写原始响应中的某个Header Header名称，值的正则表达式，重写后的值 SaveSession 在转发请求之前强制执行WebSession::save操作 无 SecureHeaders 给原始响应添加有安全作用的Header 无 SetPath 修改原始请求路径 修改后的路径 SetResponseHeader 修改原始响应中某个Header的值 Header名称，修改后的值 SetStatus 修改原始响应中的状态码 HTTP状态码，字符串或数字均可 StripPrefix 截断原始请求路径 使用数字表示要截断路径的数量 Retry 针对不同的响应进行重试 retries、statuses、methods、series RequestSize 设置允许接收的最大请求包，如果请求大小超过设置的值，返回413 Payload Too Large 请求包大小，单位Byte，默认5M ModifyRequestBody 在转发请求之前修改原始请求体内容 修改后的请求体内容 ModifyResponseBody 修改原始响应体的内容 修改后的响应体内容 全局过滤器 使用示例 局部过滤器 #局部过滤器，只对定义该过滤器的服务有效 server: port: 10010 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 #nacos服务端地址 discovery: cluster-name: 西安 gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/user/** filters: - AddRequestHeader=Author,ZhiYuanXie - id: order-service uri: lb://order-service predicates: - Path=/order/** #默认过滤器，对配置的所有的服务有效 server: port: 10010 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 #nacos服务端地址 discovery: cluster-name: 西安 gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/user/** - id: order-service uri: lb://order-service predicates: - Path=/order/** default-filters: - AddRequestHeader=Author,ZhiYuanXie 全局过滤器 @Order(-1) @Component public class AuthorizeFilter implements GlobalFilter,Ordered { @Override public Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) { //获取请求参数 MultiValueMap&lt;String, String&gt; queryParams = exchange.getRequest().getQueryParams(); //获取响应对象 ServerHttpResponse response = exchange.getResponse(); //获取authorization参数 String auth = queryParams.getFirst(&quot;authorization&quot;); if (&quot;admin&quot;.equals(auth)){ //放行 return chain.filter(exchange); } //拦截请求，响应对象设置HTTP状态码 response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } //设置过滤器优先级 public int getOrder(){ return -1; } } @Order注解 路由过滤器、默认过滤器、全局过滤器的执行顺序 每一个过滤器都必须指定一个int类型的order值，order值越小，优先级越高 全局过滤器GlobalFilter通过实现Ordered接口，或者添加@Order注解来指定order值，由我们自己实现 路由过滤器Filters和默认过滤器defaultFilter由Spring指定，默认是按声明顺序从1开始递增 当过滤器的order值都一样时，会按照 defaultFilter &gt; 路由过滤器 &gt; GlobalFilter 的顺序执行 参考以下方法查看优先级： 3.5 网关的跨域请求配置 跨域：域名不一致就是跨域，主要包括： 域名不同 域名相同，端口不同 跨域问题：浏览器禁止请求发起者与服务端发生跨域ajax请求，请求被浏览器拦截的问题 解决方案：CORS Gateway的跨域只需要简单配置即可实现 server: port: 10010 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 #nacos服务端地址 discovery: cluster-name: 西安 gateway: globalcors: #全局跨域处理 add-to-simple-url-handler-mapping: true #解决options请求被拦截的问题 cors-configurations: '[/**]': allowedOrigins: #跨域允许的网址 - &quot;http://localhost:8080/&quot; allowedMethods: #跨域允许的请求方式 - &quot;GET&quot; - &quot;POST&quot; - &quot;DELETE&quot; - &quot;PUT&quot; - &quot;OPTIONS&quot; allowedHeaders: &quot;*&quot; #跨域是否允许携带Header信息 allowedCredentials: true #跨域是否允许携带Cookie信息 maxAge: 360000 #跨域检测有效期 routes: - id: user-service uri: lb://user-service predicates: - Path=/user/** - id: order-service uri: lb://order-service predicates: - Path=/order/** default-filters: - AddRequestHeader=Author,ZhiYuanXie 第三天 八、容器化部署Docker Docker如何解决大型项目依赖关系复杂，不同组件依赖的兼容性问题？ Docker允许开发汇总将应用、依赖、函数库、配置一起打包，形成可以直接安装的镜像 Docker应用运行在容器中，使用沙箱机制，相互隔离 Docker如何解决开发、测试、生产环境有差异的问题？ Docker镜像中包含完整的运行环境，包括系统函数库，仅依赖系统的Linux内核，因此可以在任意Linux操作系统上运行 Docker与虚拟机的区别： 虚拟机是使用Hypervisor技术在操作系统中模拟硬件设备，然后运行另一个操作系统，比如在Windows系统里面运行Ubuntu系统。 特性 Docker 虚拟机 性能 接近原生 性能较差 硬盘占用 一般为MB 一般为GB 启动 秒级 分钟级 1. 概念 1.1 镜像（Image） Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起，称为镜像 1.2 容器（Container） 镜像中的应用程序运行后形成的进程就是容器，只是Docker会给容器做隔离，对外不可见 1.3 DockerHub DockerHub是一个Docker镜像的托管平台，这样的平台称为Docker Registry 国内也有类似于DockerHub的公开服务，比如网易云镜像服务、阿里云镜像库等 1.4 Docker Docker是一个CS架构的程序，由两部分组成 服务端：Docker守护进程，负责处理Docker指令，管理镜像、容器等 客户端：通过命令或RestAPI向Docker服务端发送指令，可以在本地或远程向服务端发送指令 2. 安装Docker 2.1 CentOS7安装Docker CentOS7系统下载地址： http://mirrors.163.com/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso 迅雷下载镜像文件有奇效，IDM平时下载很快，下载镜像开8线程只能跑到1M/s，迅雷直接10M/s 若之前安装过其他版本Docker，通过以下方式完成卸载： yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-lastest-logrotate \\ docker-logrotate \\ docker-selinux \\ docker-engine-selinux \\ docker-engine \\ docker-ce 安装Docker yum install -y yum-utils device-mapper-persistent-data lvm2 更新yum本地镜像源 # 删除原有配置源目录 cd /etc rm -rf yum.repos.d # 新建配置源目录 mkdir yum.repos.d # 下载阿里镜像源 cd yum.repos.d wget https://mirrors.aliyun.com/repo/Centos-7.repo # 更新yum缓存 yum makecache 更新软件源信息 参考：https://developer.aliyun.com/mirror/docker-ce?spm=a2c6h.13651102.0.0.40491b11PUGxwo # step 1: 安装必要的一些系统工具 yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3 # Step 4: 更新并安装Docker-CE yum makecache fast yum -y install docker-ce # Step 4: 开启Docker服务 service docker start 关闭防火墙（为了学习Docker，开发中应该开启指定端口） # 关闭防火墙应用 systemctl stop firewalld # 禁止开机启动防火墙 systemctl disable firewalld # 查看防火墙状态 systemctl status firewalld 配置Docker镜像源 mkdir -p /etc/docker tee /etc/docker/daemon.json &lt;&lt;-'EOF' { &quot;registry-mirrors&quot;: [&quot;https://xm9ypajm.mirror.aliyuncs.com&quot;] } EOF systemctl daemon-reload systemctl restart docker 2.2 Docker基本操作 镜像的命名规范： 镜像名称一般由两部分组成：[respository]:[tag] respository：镜像名称 tag：版本 如果没有指定tag时，默认是latest，代表最新版本的镜像 镜像相关指令 docker images：查看当前所有镜像 docker pull [respository]:[tag]：从镜像仓库中拉取指定镜像 docker save -o [Path/FileName.tar] [respository]:[tag]：将指定镜像打包 docker load -i [Path/FileName.tar]：将打包好的镜像加载到Docker中 docker rmi [respository]:[tag]：移除指定镜像 示例： 从DockerHub中拉取一个nginx镜像并查看 docker pull nginx docker images 将nginx镜像打包到本地 docker save -o ~/nginx.tar nginx:latest 将镜像文件从本地tar包加载 docker load -i ~/nginx.tar docker images 容器相关命令 docker run：创建并运行一个容器 docker pause：暂停运行 docker unpause：继续运行 docker stop：停止运行 docker start：运行 docker ps：查看所有运行容器及状态 docker logs：查看容器运行日志 docker exec：进入容器执行命令 docker rm：删除指定容器 示例： 运行一个nginx容器 docker run --name mn -p 80:80 -d nginx --name：指定容器名称 -p：指定端口映射 -d：后台运行 docker ps 查看指定容器运行日志 docker logs mn 跟踪查看运行日志：docker logs -f mn 进入容器执行命令 docker exec -it mv bash exit：退出容器 停止运行容器 docker stop mn 查看所有容器包括未运行的 docker ps -a 运行容器 docker start mn 删除容器 docker rm：只能删除未运行的容器 docker rm -f mn：强制删除容器，无论是否运行 示例：运行一个持久化存储的redis容器，并通过redis-cli设置num=666 运行容器 docker run --name my-redis -p 6379:6379 -d redis redis-server --appendonly yes 进入容器 docker exec -it my-redis bash 启动redis-cli redis-cli 设置num=666 set num 666 退出redis-cli，退出容器 exit docker exec -it my-redis redis-cli：直接进入容器中启动redis-cli 2.3 数据卷操作 数据卷的作用：将容器与数据分离，解耦合，方便操作容器内数据，保证数据安全 基本语法：docker volume [COMMAND] COMMAND create：创建一个volume inspect：显示一个或多个volume的信息 ls：列出所有的volume prune：删除未使用的volume rm：删除一个或多个指定的volume 挂载数据卷 创建并运行容器时指定数据卷的挂载目录，若数据卷不存在，则自动创建数据卷 docker run \\ --name mn \\ -p 80:80 \\ -v html:/usr/share/nginx/html \\ -d nginx 挂载目录 docker run \\ --name some-mysql \\ -e MYSQL_ROOT_PASSWORD=root \\ -p 3306:3306 \\ -v /tmp/mysql/data:/var/lib/mysql \\ -v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \\ -d mysql:latest 数据卷挂载与目录挂载 数据卷挂载耦合度低，有docker来管理目录，但是目录较深，不好找 目录挂载耦合度高，需要我们自己管理目录，不过目录容易寻找查看 3. 镜像结构 镜像就是将应用程序及其所需要的系统函数库、环境、配置、依赖打包而成的 基础镜像（BaseImage）：应用依赖的系统函数库、环境变量、配置、文件系统等 入口（Entrypoint）：镜像运行入口，一般是程序启动的脚本和参数 层（Layer）：在BaseImage基础上添加安装包、依赖、配置等，每次操作形成新的一层 镜像是分层结构，每一层称一个Layer 3.1 自定义镜像 Dockerfile：一个文本文件，指令的合集，用指令来说明要执行什么操作来构建镜像，每一个指令都会形成一层Layer 指令 说明 示例 FROM 指定BaseImage FROM centos:6 ENV 设置环境变量，可在后面指令使用 ENV key value COPY 拷贝本地文件到镜像的指定目录 COPY ./mysql-5.7.rpm /tmp RUN 执行Linux的shell命令，一般是安装过程的命令 RUN yum install gcc EXPOSE 指定容器运行时监听的端口，是给镜像使用者看的 EXPOSE 8080 ENTRYPOINT 镜像中应用的启动命令，容器运行时调用 ENTRYPOINT java -jar xx.jar # 指定基础镜像 FROM ubuntu:16.04 # 配置环境变量，JDK的安装目录 ENV JAVA_DIR=/usr/local # 拷贝jdk和java项目的包 COPY ./jdk8.tar.gz $JAVA_DIR/ COPY ./docker-demo.jar /tmp/app.jar # 安装JDK RUN cd $JAVA_DIR \\ &amp;&amp; tar -xf ./jdk8.tar.gz \\ &amp;&amp; mv ./jdk1.8.0_144 ./java8 # 配置环境变量 ENV JAVA_HOME=$JAVA_DIR/java8 ENV PATH=$PATH:$JAVA_HOME/bin # 暴露端口 EXPOSE 8090 # 入口，java项目的启动命令 ENTRYPOINT java -jar /tmp/app.jar mkdir -p /tmp/docker-demo 将docker-demo.jar jdk8.tar.gz Dockerfile上传至/tmp/docker-demo docker build -t javaweb:1.0 . docker images docker run --name web -p 8090:8090 -d javaweb:1.0 访问ip:8090/hello/count 我们发现在Dockerfile中构建jdk环境的操作是可复用的，我们应该把构建jdk环境的部分构建一个镜像，这样以后就可以直接使用了，而java:8-alpine帮我们做了这件事 3.2 DockerCompose DockerCompose可以基于Compose文件帮我们快速部署分布式应用，而无需手动一个个创建和运行容器 Compose文件是一个文本文件，通过指令定义集群中的每个容器如何运行 version: &quot;3.9&quot; services: mysql: #指定服务名称 image: mysql:8.0.21 # 指定镜像文件 environment: #设置环境变量 MYSQL_ROOT_PASSWORD: 123456 volumes: #数据卷挂载 - /tmp/mysql/data:/var/lib/mysql - /tmp/mysql/conf/hym.cnf:/etc/mysql/conf.d/hym.cnf web: #指定服务名称 build: . #从当前目录中构建镜像 ports: #设置端口号 - &quot;8090:8090&quot; 书写格式参考规范： https://docs.docker.com/compose/compose-file/compose-file-v3/ https://docs.docker.com/compose/compose-file/compose-file-v2/ 3.2.1 安装DockerCompose 参考：https://docs.docker.com/compose/install/compose-plugin/#installing-compose-on-linux-systems curl -SL https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose docker-compose所在目录/usr/local/bin/docker-compose 给docker-compose添加可执行权限chmod +x docker-compose Base自动补全命令 curl \\ -L https://raw.githubusercontent.com/docker/compose/v2.6.1/contrib/completion/bash/docker-compose \\ -o /etc/bash_completion.d/docker-compose 如果无法访问该地址，则修改本机hosts文件 echo &quot;185.199.108.133 raw.githubusercontent.com&quot; &gt;&gt; /etc/hosts 3.2.2 部署微服务集群 docker run \\ --name my-mysql8 \\ -e MYSQL_ROOT_PASSWORD=root \\ -p 3306:3306 \\ -d mysql:latest \\ --character-set-server=utf8mb4 \\ --collation-server=utf8mb4_unicode_ci FROM java:8-alpine COPY ./app.jar /tmp/app.jar ENTRYPOINT java -jar /tmp/app.jar # docker-comspose配置文件语法版本 version: 3.8 services: nacos: images: nacos/nacos-server environment: MODE: standalone ports: - &quot;8848:8848&quot; mysql: images: mysql:8.0.31 environment: MYSQL_ROOT_PASSWORD: 996748 volumes: - &quot;$PWD/mysql/data:/var/lib/mysql&quot; - &quot;$PWD/mysql/conf:/etc/mysql/conf.d&quot; user-service: build: ./user-service order-service: build: ./order-service gateway: build: ./gateway ports: - &quot;10010:10010&quot; 4. Docker镜像仓库 4.1 配置Docker信任地址 我们的私服采用的是http协议，默认不被Docker信任 # 编辑Docker服务守护进程配置文件 vi /etc/docker/daemon.json # 添加内容 &quot;insecure-registries&quot;:[&quot;http://192.168.96.130:8080&quot;] # 重新加载Docker服务守护进程 systemctl daemon-reload # 重启Docker systemctl restart docker 4.2 使用Docker部署带有图形界面的DockerRegistry version: '3.0' services: registry: image: registry volumes: - ./registry-data:/var/lib/registry ui: image: joxit/docker-registry-ui:1.5-static ports: - 8080:80 environment: - REGISTRY_TITLE=絷缘私有仓库 - REGISTRY_URL=http://registry:5000 depends_on: - registry mkdir /tmp/docker-registry-ui cd /tmp/docker-registry-ui touch docker-compose.yml vim docker-compose.yml docker-compose up -d 4.3 在私有镜像仓库推送/拉取镜像 # 将现有镜像打包成为私有镜像 docker tag nginx:latest 192.168.96.130:8080/nginx:latest # 将私有镜像推送到私有仓库 docker push 192.168.96.130:8080/nginx:latest # 将私有镜像拉取到当前环境 docker pull 192.168.96.130:8080/nginx:latest 第四天 九、消息队列RabbitMQ 同步调用的问题 微服务之间基于Feign的调用就属于同步方式，存在一些问题 耦合度高，每次加入新的需求，就需要修改原来的代码 阻塞调用，调用者需要等待提供者响应，调用链过长时等待时间相当于业务执行时间总和 资源浪费，调用者在等待过程中，不会释放请求占用的资源 级联失败：当调用链中有一个服务出现问题，name就会导致依赖于此服务的所有微服务发生故障 异步调用的方案 实现方式是事件驱动模式 优势 服务解耦 性能提升，吞吐量提高 故障隔离，服务之间没有强依赖，不担心级联失败 流量削峰 缺点 依赖于Broker的可靠性、安全性、吞吐能力 架构复杂了，业务之间没有明显的流程线，不好追踪管理 1. MQ（MessageQueue） 消息队列，字面来看就是存放消息的队列，也就是事件驱动架构中的Broker 信息 RabbitMQ ActiveMQ RocketMQ Kafka 公司/社区 Rabbit Apache 阿里 Apache 开发语言 Erlang Java Java Scala&amp;Java 协议支持 AMQP,XMPP,SMTP,STOMP OpenWire,STOMP,REST,XMPP,AMQP 自定义协议 自定义协议 可用性 高 一般 高 高 单击吞吐量 一般 差 高 非常高 消息延迟 微秒级 毫秒级 毫秒级 毫秒以内 消息可靠性 高 一般 高 一般 2. RabbitMQ快速入门 RabbitMQ是基于Erlang语言开发的开源消息通信中间件 官网地址：https://rabbitmq.com/ 2.1 安装RabbitMQ 2.1.1 下载镜像 docker pull rabbitmq:3-management 2.1.2 安装MQ docker run \\ -e RABBITMQ_DEFAULT_USER=zhiyuan \\ -e RABBITMQ_DEFAULT_PASS=123456 \\ --name my-mq \\ --hostname mql \\ -p 15672:15672 \\ -p 5672:5672 \\ -d \\ rabbitmq:3-management 若浏览器无法访问ip:15672，则按照下方操作即可： docker exec -it my-mq bash rabbitmq_plugins enable rabbitmq_management 2.2 概念解析 channel：操作MQ的工具 exchange：路由消息到队列中 queue：缓存消息 virtualhost：虚拟主机，是对queue、exchange等资源的逻辑分组 2.3 常见消息模型 2.3.1 基本消息队列（BasicQueue） 2.3.2 工作消息队列（Work Queue） 可以提高消息处理速度，避免队列消息堆积 2.3.3 发布订阅（Publish Subscribe） 发布订阅模式允许将同一消息发送给多个消费者，实现方式就是加入交换机exchange Fanout Exchange：广播 Direct Exchange：路由 Topic Exchange：主题 3. SpringAMQP 3.1 入门案例：消息的发送 引入AMQP依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-amqp&lt;/artifactId&gt; &lt;/dependency&gt; 在publisher服务中编写application.yml，添加mq连接信息： spring： rabbitmq: host: 192.168.174.130 port: 5672 virtual-host: / username: zhiyuan password: 123456 在publisher服务中新建一个测试类，编写测试方法： @RunWith(SpringRunner.class) @SpringBootTest public class SpringAMQPTest{ @Autowired private RabbitTemplate rabbitTemplate; @Test public void testSimpleQueue(){ String queueName = &quot;simple.queue&quot;; String message = &quot;Hello,SpringAMQP&quot;; rabbitTemplate.covertAndSend(queueName,message); } } 3.2 入门案例：消息的接收 在consumer服务中编写application.yml，添加mq连接信息： spring: rabbitmq: host: 192.168.174.130 port: 5672 virtual-host: / username: zhiyuan password: 123456 在consumer服务中新建一个类，编写消费逻辑： @Component public class SpringRabbitListener{ @RabbitListener(queues = {&quot;simple.queue&quot;}) public void listenSimpleQueueMessage(String msg) throws InterruptedException{ System.out.println(&quot;SpringConsumer接受到消息：【&quot; + msg + &quot;】&quot;); } } 注意事项：消息一旦被消费就会从队列中移除，RabbitMQ没有消息回溯功能 3.3 工作队列案例 基本思路 在publisher服务中定义测试方法，每秒产生50条消息发送到simple.queue 在consumer服务中定义两个消息监听者，都监听simple.queue 消费者1每秒钟处理50条消息，消费者2每秒钟处理10条消息 @Test public void testSendMessageToWorkQueue() throws InterruptedException { String queueName = &quot;simple.queue&quot;; String message = &quot;Hello,SpringAMQP-MESSAGE_&quot;; for (int i = 0; i &lt; 50; i++) { rabbitTemplate.convertAndSend(queueName,message + i); Thread.sleep(20); } } @RabbitListener(queues = {&quot;simple.queue&quot;}) public void listenWorkQueueMessage1(String msg) throws InterruptedException { System.out.println(&quot;SpringConsumer1接收到消息“【&quot; + msg + &quot;】&quot; + LocalTime.now()); Thread.sleep(20); } @RabbitListener(queues = {&quot;simple.queue&quot;}) public void listenWorkQueueMessage2(String msg) throws InterruptedException { System.err.println(&quot;SpringConsumer2接收到消息“【&quot; + msg + &quot;】&quot; + LocalTime.now()); Thread.sleep(200); } 我们发现一个现象，消费者1很快处理完消息后就停止处理了，而把所有的消息都交由速度较慢的消费者2，这是由于预取消息导致的，我们可以通过修改application.yml文件来限制预取消息的上限 spring: rabbitmq: host: 192.168.174.130 port: 5672 virtual-host: / username: zhiyuan password: 123456 listener: simple: prefetch: 1 # 每次只能获取一条消息，处理完成才能发获取下一条消息 3.4 发布订阅案例 3.4.1 Fanout Exchange：将接受到的消息路由到每一个与其绑定的queue 实现思路 在consumer服务中利用代码声明队列，交换机，并将二者绑定 在consumer服务中编写两个消费者方法，分别监听fanout.queue1和fanoput.queue2 在publisher服务中编写测试方法，向zhiyuan.fanout发送消息 @Configuration public class FanoutConfig { @Bean public FanoutExchange fanoutExchange(){ return new FanoutExchange(&quot;zhiyuan.fanout&quot;); } @Bean public Queue fanoutQueue1(){ return new Queue(&quot;fanout.queue1&quot;); } @Bean public Binding bindingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){ return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange); } @Bean public Queue fanoutQueue2(){ return new Queue(&quot;fanout.queue2&quot;); } @Bean public Binding bindingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){ return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange); } } @RabbitListener(queues = {&quot;fanout.queue1&quot;}) public void listenFanoutQueue1(String msg) throws InterruptedException { System.err.println(&quot;SpringConsumer1接收到FanoutQueue1消息“【&quot; + msg + &quot;】&quot; + LocalTime.now()); } @RabbitListener(queues = {&quot;fanout.queue2&quot;}) public void listenFanoutQueue2(String msg) throws InterruptedException { System.err.println(&quot;SpringConsumer2接收到FanoutQueue2消息“【&quot; + msg + &quot;】&quot; + LocalTime.now()); } @Test public void testSendMessageToFanoutQueue() throws InterruptedException { String exchangeName = &quot;zhiyuan.fanout&quot;; String message = &quot;Hello,Fanout EveryOne!&quot;; rabbitTemplate.convertAndSend(exchangeName,&quot;&quot;,message); } 3.4.2 Direct Exchange：将接受到的消息根据规则路由到指定的queue 实现思路 每一个queue都与Exchange设置一个BindingKey 发布者发送消息时，指定消息的RoutingKey Exchange将消息路由到BindingKey与消息RoutingKey一致的队列 利用@RabbitListener声明Exchange、Queue、RoutingKey 在consumer服务中编写两个消费者方法，分别监听direct.queue1和direct.queue2 在publisher中编写测试方法，向zhiyuan.direct发送消息 @RabbitListener(bindings = @QueueBinding( value = @Queue(name = &quot;direct.queue1&quot;), exchange = @Exchange(name = &quot;zhiyuan.direct&quot;,type = ExchangeTypes.DIRECT), key = {&quot;red&quot;,&quot;blue&quot;} )) public void listenDirectQueue1(String msg){ System.err.println(&quot;SpringConsumer接收到DirectQueue1消息“【&quot; + msg + &quot;】&quot; + LocalTime.now()); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = &quot;direct.queue2&quot;), exchange = @Exchange(name = &quot;zhiyuan.direct&quot;,type = ExchangeTypes.DIRECT), key = {&quot;red&quot;,&quot;yellow&quot;} )) public void listenDirectQueue2(String msg){ System.err.println(&quot;SpringConsumer接收到DirectQueue2消息“【&quot; + msg + &quot;】&quot; + LocalTime.now()); } @Test public void testSendMessageToDirectQueue1() throws InterruptedException { String exchangeName = &quot;zhiyuan.direct&quot;; String message = &quot;Hello,Direct key is blue&quot;; rabbitTemplate.convertAndSend(exchangeName,&quot;blue&quot;,message); } @Test public void testSendMessageToDirectQueue2() throws InterruptedException { String exchangeName = &quot;zhiyuan.direct&quot;; String message = &quot;Hello,Direct key is yellow&quot;; rabbitTemplate.convertAndSend(exchangeName,&quot;yellow&quot;,message); } @Test public void testSendMessageToDirectQueue() throws InterruptedException { String exchangeName = &quot;zhiyuan.direct&quot;; String message = &quot;Hello,Direct key is red&quot;; rabbitTemplate.convertAndSend(exchangeName,&quot;red&quot;,message); } 3.4.3 TopicExchange：与DirectExchange类似，区别在于routingKey必须是多个单词列表，并且以.分割 Queue和Exchange指定BindingKey时可以使用通配符 #：代指0个或多个单词 *：代指一个单词 实现思路 利用@RabbitListener声明Exchange、Queue、RoutingKey 在consumer服务中编写两个消费者方法，分别监听topic.queue1和topic.queue2 在publisher中编写测试方法，向zhiyuan.topic发送消息 @RabbitListener(bindings = @QueueBinding( value = @Queue(name = &quot;topic.queue1&quot;), exchange = @Exchange(name = &quot;zhiyuan.topic&quot;,type = ExchangeTypes.TOPIC), key = &quot;china.#&quot; )) public void listenTopicQueue1(String msg){ System.err.println(&quot;SpringConsumer接收到TopicQueue1消息“【&quot; + msg + &quot;】&quot; + LocalTime.now()); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = &quot;topic.queue2&quot;), exchange = @Exchange(name = &quot;zhiyuan.topic&quot;,type = ExchangeTypes.TOPIC), key = &quot;#.news&quot; )) public void listenTopicQueue2(String msg){ System.err.println(&quot;SpringConsumer接收到TopicQueue2消息“【&quot; + msg + &quot;】&quot; + LocalTime.now()); } @Test public void testSendMessageToTopicQueue() throws InterruptedException { String exchangeName = &quot;zhiyuan.topic&quot;; String message = &quot;Hello,Topic key is china.news&quot;; rabbitTemplate.convertAndSend(exchangeName,&quot;china.news&quot;,message); } 4.SpringAMQP消息转换器 在SpringAMQP的发送方法中，接收消息的类型时Object，也就是说我们可以发送任意对象类型的消息，SpringAMQP最终会帮我们序列化为字节后发送，content-type 为 application/x-java-serialized-object ，默认直接以对象方式传输是很不安全的而且消息很长 SpringAMQP对消息对象的处理是由 org.springframework.amqp.support.converter.MessageConverter完成的 默认实现：SimpleMessageConverter 基于JDK的 ObjectOutputStream 完成序列化 如果要修改只需要定义一个MessageConverter类型的Bean即可 创建 object.queue 队列 @Bean public Queue objectQueue(){ return new Queue(&quot;object.queue&quot;); } 引入依赖 &lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt; &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.dataformat&lt;/groupId&gt; &lt;artifactId&gt;jackson-dataformat-xml&lt;/artifactId&gt; &lt;version&gt;2.9.10&lt;/version&gt; &lt;/dependency&gt; 在publisher、consumer服务声明MessageConverter @Bean public MessageConverter jsonMessageConverter(){ return new Jackson2JsonMessageConverter(); } 测试发送HashMap类型消息 @Test public void testSendObjectMessageToSimpleQueue(){ String queueName = &quot;object.queue&quot;; HashMap&lt;String, String&gt; map = new HashMap&lt;&gt;(); map.put(&quot;name&quot;,&quot;zhiyuan&quot;); map.put(&quot;gender&quot;,&quot;male&quot;); rabbitTemplate.convertAndSend(queueName,map); } 测试接收HashMap类型消息 @RabbitListener(queues = &quot;object.queue&quot;) public void listenObjectQueue(Map&lt;String,String&gt; msg){ System.out.println(&quot;收到消息：【&quot; + msg + &quot;】&quot;); } 十、分布式搜索Elasticsearch 1. 初识elasticsearch 介绍：elasticsearch是一款非常强大的开源搜索引擎，可以帮助我们从海量数据中快速找到需要的内容 elasticsearch结合kibana、Logstash、Beats，也就是说elastic stack（ELK）被广泛应用于日志数据分析、实时监控等领域 elasticsearch是elastic stack的核心，负责存储、搜索、分析数据 elasticsearch的发展 Lucene是一个Java语言的搜索引擎类库，是Apache公司的顶级项目，由DougCutting于1999年研发 官网地址：https://lucene.apache.org 优势： 易扩展 高性能（基于倒排索引） 缺点： 只限于java语言开发 学习曲线陡峭 不支持水平扩展 相比于lucene，elasticsearch具有以下优势 支持分布式，可水平扩展 提供Restful接口，可被任何语言调用 搜索引擎技术排名 Elasticsearch：开源的分布式搜索引擎 Splunk：商业项目收费 Solr： Apache的开源搜索引擎 1.1. 正向索引和倒排索引 传统数据库采用正向索引 elaticsearch采用倒排索引 文档（document）：每条数据就是一个文档 词条（term）：文档按照语义分成的词语 1.2 文档 elasticsearch是面向文档存储的，可以是数据库中的一条商品数据，一个订单信息 文档数据会被序列化为json格式存储在elasticsearch中 1.3 索引 索引（index）：相同类型文档的集合 映射（mapping）：索引中文档的字段约束信息，类似表的结构约束 1.4 概念对比 MySQL擅长事务类型操作，可以确保数据的安全和一致性 Elasticsearch擅长海量数据的搜索、分析、计算 2. 安装Elasticsearch 2.1 创建网络 因为我们还需要部署kibana容器，因此需要让es和kibana容器互联，这里先创建一个网络 docker network create es-net 2.2 加载镜像 docker pull elasticsearch:7.12.1 2.3 运行 docker run -d \\ --name es \\ -e &quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot; \\ -e &quot;discovery.type=single-node&quot; \\ -v es-data:/usr/share/elasticsearch/data \\ -v es-plugins:/usr/share/elasticsearch/plugins \\ --privileged \\ --network es-net \\ -p 9200:9200 \\ -p 9300:9300 \\ elasticsearch:7.12.1 命令解释： -e &quot;cluster.name=es-docker-cluster&quot;：设置集群名称 -e &quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;：设置JAVA程序运行内存 -e &quot;discovery.type=single-node&quot;：设置es为单例模式 --privileged：授予数据卷访问权 --network es-net：加入名为es-net的网络 2.4 部署kibana docker pull kibana:7.12.1 docker run -d \\ --name kibana \\ -e ELASTICSEARCH_HOSTS=http://es:9200 \\ --network=es-net \\ -p 5601:5601 \\ kibana:7.12.1 3. 分词器 es在创建倒排索引时需要对文档分词，在搜索时，需要对用户输入内容分词，但默认的分词规则对中文处理并不友好 POST /_analyze { &quot;analyzer&quot;:&quot;standard&quot;, &quot;text&quot;:&quot;神探狄仁杰&quot; } 语法说明 POST：请求方式 /_analyze：请求路径，省略了http://IP:9200，kibana会帮我们补充 请求参数：JSON风格 analyzer：分词器类型，这里默认时standard分词器 text：要分词的内容 elasticsearch 的分词器对中文支持不好，一般我们会采用IK分词器 测试默认分词器效果 POST /_analyze { &quot;text&quot;:&quot;神探狄仁杰&quot;, &quot;analyzer&quot;:&quot;standard&quot; } { &quot;tokens&quot; : [ { &quot;token&quot; : &quot;神&quot;, &quot;start_offset&quot; : 0, &quot;end_offset&quot; : 1, &quot;type&quot; : &quot;&lt;IDEOGRAPHIC&gt;&quot;, &quot;position&quot; : 0 }, { &quot;token&quot; : &quot;探&quot;, &quot;start_offset&quot; : 1, &quot;end_offset&quot; : 2, &quot;type&quot; : &quot;&lt;IDEOGRAPHIC&gt;&quot;, &quot;position&quot; : 1 }, { &quot;token&quot; : &quot;狄&quot;, &quot;start_offset&quot; : 2, &quot;end_offset&quot; : 3, &quot;type&quot; : &quot;&lt;IDEOGRAPHIC&gt;&quot;, &quot;position&quot; : 2 }, { &quot;token&quot; : &quot;仁&quot;, &quot;start_offset&quot; : 3, &quot;end_offset&quot; : 4, &quot;type&quot; : &quot;&lt;IDEOGRAPHIC&gt;&quot;, &quot;position&quot; : 3 }, { &quot;token&quot; : &quot;杰&quot;, &quot;start_offset&quot; : 4, &quot;end_offset&quot; : 5, &quot;type&quot; : &quot;&lt;IDEOGRAPHIC&gt;&quot;, &quot;position&quot; : 4 } ] } IK分词器：https://github.com/medcl/elasticsearch-analysis-ik 3.1 安装分词器 # 进入容器内部 docker exec -it elasticsearch /bin/bash # 在线下载插件并安装 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip # 退出 exit # 重启容器 docker restart elasticsearch 在线安装可能会失败，解决办法离线安装 docker volume inspect es-plugins cd /var/lib/docker/volume/es-plugins/_data # 上传插件压缩包解压后目录 # 重启容器 docker restart es # 查看es日志 docker logs -f es 3.2 IK分词器 IK分词器包含两种模式 ik_smart：最少切分 ik_max_word：最细切分 测试请求： POST /_analyze { &quot;text&quot;:&quot;神探狄仁杰&quot;, &quot;analyzer&quot;:&quot;ik_smart&quot; } 返回结果 { &quot;tokens&quot; : [ { &quot;token&quot; : &quot;神&quot;, &quot;start_offset&quot; : 0, &quot;end_offset&quot; : 1, &quot;type&quot; : &quot;CN_CHAR&quot;, &quot;position&quot; : 0 }, { &quot;token&quot; : &quot;探&quot;, &quot;start_offset&quot; : 1, &quot;end_offset&quot; : 2, &quot;type&quot; : &quot;CN_CHAR&quot;, &quot;position&quot; : 1 }, { &quot;token&quot; : &quot;狄仁杰&quot;, &quot;start_offset&quot; : 2, &quot;end_offset&quot; : 5, &quot;type&quot; : &quot;CN_WORD&quot;, &quot;position&quot; : 2 } ] } 3.3 IK分词器的拓展和停用字典 要拓展IK分词器的词库，只需要修改一个IK分词器目录中config目录下的IKAnalyzer.cfg.xml文件 &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt; &lt;!DOCTYPE properties SYSTEM &quot;http://java.sun.com/dtd/properties.dtd&quot;&gt; &lt;properties&gt; &lt;comment&gt;IK Analyzer扩展配置&lt;/comment&gt; &lt;!-- 用户可以在这里配置自己的扩展字典 --&gt; &lt;entry key=&quot;ext_dict&quot;&gt;ext.dic&lt;/entry&gt; &lt;!-- 用户可以在这里配置自己的扩展停止词典 --&gt; &lt;entry key=&quot;ext_stopwords&quot;&gt;stopword.dic&lt;/entry&gt; &lt;/properties&gt; 在config目录下对应的字典文件中添加扩展词语，或创建自己的扩展字典加入扩展词语，词语以换行符分隔 4. 索引库操作 4.1 mapping属性 mapping是对索引库中文档的约束 type：字段数据类型，常见的简单类型有： 字符串：text（可分词的文本）关键字：keyword（精确值） 数值：long、integer、short、byte、double、float 布尔：boolean 日期：date 对象：object index：是否创建索引，默认为true analyzer：分词器 properties：某字段的子字段 4.2 创建索引库 ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示 PUT /索引库名称 { &quot;mappings&quot;:{ &quot;properties&quot;:{ &quot;字段名1&quot;:{ &quot;type&quot;:&quot;text&quot;, &quot;analzyer&quot;:&quot;ik_smart&quot; }, &quot;字段名2&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;index&quot;:false }, &quot;字段名3&quot;:{ &quot;properties&quot;:{ &quot;子字段&quot;: { &quot;type&quot;:&quot;keyword&quot; } } } } } } 4.3 查看、删除索引库 查看索引库 GET /索引库名 删除索引库 DELETE /索引库名 4.4 修改索引库 索引库和mapping一旦创建无法修改，但是可以加入新的字段 PUT /索引库名/_mapping { &quot;properties&quot;:{ &quot;新字段名&quot;:{ &quot;type&quot;:&quot;integer&quot; } } } 5. 文档操作 5.1 新增文档 POST /索引库名/_doc/文档ID { &quot;字段1&quot;:&quot;值1&quot;, &quot;字段2&quot;:&quot;值2&quot;, &quot;字段3&quot;:{ &quot;子属性1&quot;:&quot;值3&quot;, &quot;子属性2&quot;:&quot;值4&quot; } } 5.2 查询文档 GET /索引库名/_doc/文档ID 5.3 删除文档 DELETE /索引库名/_doc/文档ID 5.4 修改文档 5.4.1 全量修改 会删除旧文档，添加新文档 PUT /索引库名/_doc/文档ID { &quot;字段1&quot;:&quot;值1&quot;, &quot;字段2&quot;:&quot;值2&quot; } 5.4.2 增量修改 只修改指定字段值 POST /索引库名/_update/文档ID { &quot;doc&quot;:{ &quot;字段名&quot;:&quot;新值&quot; } } 6. RestClient ES中支持两种地理坐标类型： geo_point：由纬度（latitude）和经度（longitude）确定的一个点 geo_shape：由多个geo_point组成的复杂几何图形 ES中字段拷贝可以使用copy_to属性将当前字段拷贝到指定字段 &quot;all&quot;:{ &quot;type&quot;:&quot;text&quot;, &quot;analyzer&quot;:&quot;ik_max_word&quot; }, &quot;brand&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;copy_to&quot;:&quot;all&quot; } PUT /hotel { &quot;mappings&quot;:{ &quot;properties&quot;: { &quot;id&quot;:{ &quot;type&quot;:&quot;keyword&quot; }, &quot;name&quot;:{ &quot;type&quot;:&quot;text&quot;, &quot;analyzer&quot;:&quot;ik_max_word&quot;, &quot;copy_to&quot;: &quot;all&quot; }, &quot;address&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;index&quot;:false }, &quot;price&quot;:{ &quot;type&quot;:&quot;integer&quot; }, &quot;score&quot;:{ &quot;type&quot;:&quot;integer&quot; }, &quot;brand&quot;:{ &quot;type&quot;:&quot;keyword&quot; }, &quot;city&quot;:{ &quot;type&quot;: &quot;keyword&quot; }, &quot;starName&quot;:{ &quot;type&quot;:&quot;keyword&quot; }, &quot;business&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;copy_to&quot;: &quot;all&quot; }, &quot;location&quot;:{ &quot;type&quot;:&quot;geo_point&quot; }, &quot;pic&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;index&quot;:false }, &quot;all&quot;:{ &quot;type&quot;:&quot;text&quot;, &quot;analyzer&quot;: &quot;ik_max-word&quot; } } } } 参不参与分词 &quot;type&quot;:&quot;keyword&quot;：不参与分词 &quot;type&quot;:&quot;text&quot;：参与分词 &quot;analyzer&quot;：指定分词器 参不参与搜索： &quot;index&quot;:true：参与搜索 &quot;index&quot;:false：不参与搜索 多个字段均参与搜索： 添加一个新的字段，并将要参与搜索的字段copy_to新的字段 &quot;新字段&quot;:{ &quot;type&quot;:&quot;text&quot;, &quot;analyzer&quot;:&quot;ik_max_word&quot; }, &quot;city&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;copy_to&quot;:&quot;新字段&quot; } 6.1 初始化 JavaRestClient 引入依赖 &lt;properties&gt; &lt;elasticsearch.version&gt;7.12.1&lt;/elasticsearch.version&gt; &lt;/properties&gt; &lt;dependency&gt; &lt;groupId&gt;org.elasticsearch.client&lt;/groupId&gt; &lt;artifactId&gt;elasticsearch-rest-high-level-client&lt;/artifactId&gt; &lt;version&gt;${elasticsearch.version}&lt;/version&gt; &lt;/dependency&gt; 书写测试类 public class HotelIndexTest { private RestHighLevelClient client; @BeforeEach void setUp(){ this.client = new RestHighLevelClient(RestClient.builder( HttpHost.create(&quot;http://192.168.174.130:9200&quot;) )); } @AfterEach void tearDown() throws IOException{ this.client.close(); } @Test public void testInit(){ System.out.println(client); } } 6.2 RestClient实现索引库的CRUD 6.2.1 创建索引库 将DSL语句创建为常量使用，内容如下，需要去掉请求方式和请求地址，变成正常的JSON格式 { &quot;mappings&quot;:{ &quot;properties&quot;: { &quot;id&quot;:{ &quot;type&quot;:&quot;keyword&quot; }, &quot;name&quot;:{ &quot;type&quot;:&quot;text&quot;, &quot;analyzer&quot;:&quot;ik_max_word&quot;, &quot;copy_to&quot;: &quot;all&quot; }, &quot;address&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;index&quot;:false }, &quot;price&quot;:{ &quot;type&quot;:&quot;integer&quot; }, &quot;score&quot;:{ &quot;type&quot;:&quot;integer&quot; }, &quot;brand&quot;:{ &quot;type&quot;:&quot;keyword&quot; }, &quot;city&quot;:{ &quot;type&quot;: &quot;keyword&quot; }, &quot;starName&quot;:{ &quot;type&quot;:&quot;keyword&quot; }, &quot;business&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;copy_to&quot;: &quot;all&quot; }, &quot;location&quot;:{ &quot;type&quot;:&quot;geo_point&quot; }, &quot;pic&quot;:{ &quot;type&quot;:&quot;keyword&quot;, &quot;index&quot;:false }, &quot;all&quot;:{ &quot;type&quot;:&quot;text&quot;, &quot;analyzer&quot;: &quot;ik_max_word&quot; } } } } @Test public void createHotelIndex() throws IOException { //创建提交创建索引库请求的对象 CreateIndexRequest hotelRequest = new CreateIndexRequest(&quot;hotel&quot;); //为请求对象设置请求DSL语句及请求格式 hotelRequest.source(HotelConstants.HOTEL_MAPPING_TEMPLATE, XContentType.JSON); //创建索引库 client.indices().create(hotelRequest, RequestOptions.DEFAULT); } indices()：此方法返回的对象中包含所有有关索引库的操作方法 6.2.2 删除索引库 @Test public void deleteHotelIndex() throws IOException { //创建提交删除索引库请求的对象 DeleteIndexRequest hotelRequest = new DeleteIndexRequest(&quot;hotel&quot;); client.indices().delete(hotelRequest,RequestOptions.DEFAULT); } 6.2.3 判断索引库是否存在 @Test public void existsHotelIndex() throws IOException { //创建提交查询索引库请求的对象 GetIndexRequest hotelRequest = new GetIndexRequest(&quot;hotel&quot;); boolean exists = client.indices().exists(hotelRequest, RequestOptions.DEFAULT); System.out.println(exists?&quot;索引库已存在&quot;:&quot;索引库不存在&quot;); } 6.2.4 查询索引库 @Test public void getHotelIndex() throws IOException{ //创建提交查询索引库请求的对象 GetIndexRequest hotelRequest = new GetIndexRequest(&quot;hotel&quot;); GetIndexResponse hotelResponse = client.indices().get(hotelRequest, RequestOptions.DEFAULT); Map&lt;String, MappingMetadata&gt; mappings = hotelResponse.getMappings(); ObjectMapper objectMapper = new ObjectMapper(); String mappingsJSON = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mappings); System.out.println(mappingsJSON); } 6.3 RestClient实现文档的CRUD 6.3.1 创建文档 @Test public void createDoc() throws IOException{ Hotel hotel = hotelService.getById(39106L); IndexRequest indexRequest = new IndexRequest(&quot;hotel&quot;).id(hotel.getId().toString()); HotelDoc hotelDoc = new HotelDoc(hotel); indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON); client.index(indexRequest, RequestOptions.DEFAULT); } 6.3.2 查询文档 @Test public void getDoc() throws IOException{ GetRequest getRequest = new GetRequest(&quot;hotel&quot;).id(&quot;39106&quot;); GetResponse response = client.get(getRequest, RequestOptions.DEFAULT); System.out.println(JSON.toJSONString(response.getSource(),SerializerFeature.PrettyFormat)); } 6.3.3 删除文档 @Test public void deleteDoc() throws IOException{ DeleteRequest deleteRequest = new DeleteRequest(&quot;hotel&quot;).id(&quot;39106&quot;); client.delete(deleteRequest,RequestOptions.DEFAULT); } 6.3.4 修改文档 全量更新与创建文档操作并无差别 增量更新 @Test public void modifyDoc() throws IOException{ UpdateRequest updateRequest = new UpdateRequest(&quot;hotel&quot;,&quot;39106&quot;); updateRequest.doc( &quot;price&quot;,&quot;350&quot;, &quot;starName&quot;,&quot;五钻&quot; ).upsert( &quot;area&quot;,&quot;120&quot; ); client.update(updateRequest,RequestOptions.DEFAULT); } 6.3.5 批量新增文档 @Test public void testBulk() throws IOException{ BulkRequest bulkRequest = new BulkRequest(); List&lt;Hotel&gt; hotelList = hotelService.list(); for (Hotel hotel : hotelList) { HotelDoc hotelDoc = new HotelDoc(hotel); bulkRequest.add(new IndexRequest(&quot;hotel&quot;) .id(hotelDoc.getId().toString()) .source(JSON.toJSONString(hotelDoc),XContentType.JSON)); } client.bulk(bulkRequest,RequestOptions.DEFAULT); } GET /hotel/_search 7. DSL语法 7.1 查询所有 match_all 示例 GET /indexName/_search { &quot;query&quot;:{ &quot;[queryType]&quot;:{ &quot;[queryCondition|field]&quot;:&quot;[conditionValue|value]&quot; } } } 实例 //查询所有 GET /hotel/_search { &quot;query&quot;:{ &quot;match_all&quot;:{} } } 7.2 全文检索 match_query multi_match_query 示例 GET /indexName/_search { &quot;query&quot;:{ &quot;match&quot;:{ &quot;[fieldName]&quot;:&quot;[textValue]&quot; } } } GET /indexName/_search { &quot;query&quot;: { &quot;multi_match&quot;: { &quot;query&quot;: &quot;[textValue]&quot;, &quot;fields&quot;: [&quot;field1&quot;,&quot;field2&quot;,&quot;field3&quot;...] } } } 实例 GET /hotel/_search { &quot;query&quot;: { &quot;match&quot;: { &quot;all&quot;: &quot;外滩&quot; } } } GET /hotel/_search { &quot;query&quot;: { &quot;multi_match&quot;: { &quot;query&quot;: &quot;如家&quot;, &quot;fields&quot;: [&quot;brand&quot;,&quot;name&quot;,&quot;business&quot;] } } } 7.3 精确查询 ids range:根据值的范围查询 gte：大于等于 lte：小于等于 gt：大于 lt：小于 term:根据词条精确值查询 示例 GET /indexName/_search { &quot;query&quot;:{ &quot;term&quot;:{ &quot;[fieldName]&quot;:{ &quot;value&quot;:&quot;[value]&quot; } } } } GET /indexName/_search { &quot;query&quot;:{ &quot;range&quot;:{ &quot;[fieldName]&quot;:{ &quot;gte&quot;:[numValue], &quot;lte&quot;:[numValue] } } } } 实例 GET /hotel/_search { &quot;query&quot;:{ &quot;term&quot;: { &quot;city&quot;: { &quot;value&quot;: &quot;上海&quot; } } } } GET /hotel/_search { &quot;query&quot;: { &quot;range&quot;: { &quot;price&quot;: { &quot;gte&quot;: 100, &quot;lte&quot;: 300 } } } } 7.4 地理查询 geo_distance geo_bounding_box 示例 GET /indexName/_search { &quot;query&quot;:{ &quot;geo_distance&quot;:{ &quot;distance&quot;:&quot;15km&quot;, &quot;[fieldName]&quot;:&quot;31.21,121.5&quot; } } } 实例 GET /hotel/_search { &quot;query&quot;: { &quot;geo_distance&quot;:{ &quot;distance&quot;:&quot;15km&quot;, &quot;location&quot;:&quot;31.21,121.5&quot; } } } 7.5 复合查询 bool function_score:算分函数查询，可以控制文档相关性算分，控制文档排名 7.5.1 相关性算分查询 TF-IDF：在elasticsearch5.0之前，会随着词频增加而越来越大 BM25：在elasticsearch5.0之后，会随着词频增加而增大，蛋增长曲线趋于水平 示例 GET /indexName/_search { &quot;query&quot;:{ &quot;function_score&quot;:{ &quot;query&quot;:{ &quot;match&quot;:{ &quot;all&quot;:&quot;外滩&quot; } }, &quot;functions&quot;:[ { &quot;filter&quot;:{ &quot;term&quot;:{ &quot;id&quot;:&quot;1&quot; } }, &quot;weight&quot;:10 } ], &quot;boost_mode&quot;:&quot;multiply&quot; } } } 实例 //让如家品牌酒店排名更靠前一些 GET /hotel/_search { &quot;query&quot;:{ &quot;function_score&quot;:{ &quot;query&quot;:{ &quot;match&quot;:{ &quot;all&quot;:&quot;外滩&quot; } }, &quot;functions&quot;:[ { &quot;filter&quot;:{ &quot;term&quot;:{ &quot;brand&quot;:&quot;如家&quot; } }, &quot;weight&quot;:2 } ], &quot;boost_mode&quot;:&quot;sum&quot; } } } 7.5.2 布尔查询 布尔查询是一个或多个查询字句的组合，子查询的组合方式有如下： must：必须匹配每个子查询，参与算分，类似 与 should：选择性匹配子查询，参与算分，类似 或， must_not：必须不匹配，不参与算分，类似 非 filter：必须匹配，不参与算分 实例 GET /hotel/_search { &quot;query&quot;:{ &quot;bool&quot;:{ &quot;must&quot;:[ {&quot;term&quot;:{&quot;city&quot;:&quot;上海&quot;}} ], &quot;should&quot;:[ {&quot;term&quot;:{&quot;brand&quot;:&quot;皇冠假日&quot;}}, {&quot;term&quot;:{&quot;brand&quot;:&quot;华美达&quot;}} ], &quot;must_not&quot;:[ {&quot;range&quot;:{&quot;price&quot;:{&quot;lte&quot;:500}}} ], &quot;filter&quot;:[ {&quot;range&quot;:{&quot;score&quot;:{&quot;gte&quot;:45}}} ] } } } GET /hotel/_search { &quot;query&quot;:{ &quot;bool&quot;:{ &quot;must&quot;:[ {&quot;match&quot;:{&quot;name&quot;:&quot;如家&quot;}} ], &quot;must_not&quot;:[ {&quot;range&quot;:{&quot;price&quot;:{&quot;gt&quot;:400}}} ], &quot;filter&quot;:[ {&quot;geo_distance&quot;:{&quot;distance&quot;:&quot;10km&quot;,&quot;location&quot;:{&quot;lat&quot;:&quot;31.21&quot;,&quot;lon&quot;:&quot;121.5&quot;}} ] } } } 8. 排序 elasticsearch支持对搜索结果排序，默认是根据相关度算分(_score)来排序，可排序的字段类型有： keyword 数值 地理坐标 日期类型 GET /indexName/_search { &quot;query&quot;:{ &quot;match_all&quot;:{} }, &quot;sort&quot;:[ { &quot;[fieldName]&quot;:&quot;[desc|asc]&quot; } ] } GET /indexName/_search { &quot;query&quot;:{ &quot;match_all&quot;:{} }, &quot;sort&quot;:[ { &quot;_geo_distance&quot;:{ &quot;location&quot;:&quot;lat,lau&quot;, &quot;order&quot;:&quot;[desc|asc]&quot;, &quot;unit&quot;:&quot;km&quot; } } ] } 9. 分页 elasticsearch默认情况下只返回top10的数据，而如果要查询更多的数据就需要修改分页参数 from：分页开始的位置，默认为0 size：每页文档条数 GET /indexName/_search { &quot;query&quot;:{ &quot;match_all&quot;:{} }, &quot;from&quot;:100, &quot;size&quot;:20, &quot;sort&quot;:[ {&quot;price&quot;:&quot;asc&quot;} ] } 深度分页问题 ES是分布式的，所以会面临深度分页问题 首先在每个数据分片上都排序并查询前1000条文档 然后将所有节点的结果聚合，在内存中重新排序选出前1000条文档 如果搜索页数过深，或者结果集（from+size）越大，对内存和CPU的消耗也越高，所以ES设定结果集查询上限是10000 解决方案 search_after：分页时需要排序，原理是从上一次的排序值开始，查询下一页数据【官方推荐】 scroll：原理是将排序数据形成快照，保存在内存【官方不推荐】 10. 高亮 高亮就是在搜索结果中吧搜索关键字突出显示 高亮显示的原理 后端将搜索结果中的关键字用指定标签标记出来 前端对指定标签添加高亮样式 默认情况下，ES搜索字段必须与高亮字段一致，才会正常返回高亮字段内容，如果要改变默认情况，应该将require_field_match改为false GET /hotel/_search { &quot;query&quot;:{ &quot;match&quot;:{ &quot;[fieldName]&quot;:&quot;[textValue]&quot; } }, &quot;highlight&quot;:{ &quot;fields&quot;:{ &quot;[fieldName]&quot;:{ &quot;pre_tags&quot;:&quot;&lt;em&gt;&quot;, &quot;post_tags&quot;:&quot;&lt;/em&gt;&quot;, &quot;require_field_match&quot;:false } } } } 11. RestClient查询文档 11.1 查询所有 代码 @Test void testMatchAll() throws IOException { SearchRequest hotelRequest = new SearchRequest(&quot;hotel&quot;); hotelRequest.source().query(QueryBuilders.matchAllQuery()); SearchResponse searchResponse = client.search(hotelRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } DSL GET /hotel/_search { &quot;query&quot;:{ &quot;match_all&quot;:{} } } RestAPI中构建DSL都是通过HighLevelRestClient中的source()方法实现的 11.2 全文检索 代码 @Test void testMatch() throws IOException { SearchRequest hotelRequest = new SearchRequest(&quot;hotel&quot;); hotelRequest.source().query(QueryBuilders.matchQuery(&quot;all&quot;,&quot;如家&quot;)); SearchResponse searchResponse = client.search(hotelRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } @Test void testMultiMatch() throws IOException{ SearchRequest hotelRequest = new SearchRequest(&quot;hotel&quot;); hotelRequest.source().query(QueryBuilders.multiMatchQuery(&quot;如家&quot;,&quot;business&quot;,&quot;name&quot;)); SearchResponse searchResponse = client.search(hotelRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } DSL GET /hotel/_search { &quot;query&quot;:{ &quot;match&quot;:{ &quot;all&quot;:&quot;如家&quot; } } } GET /hotel/_search { &quot;query&quot;:{ &quot;match&quot;:{ &quot;query&quot;:&quot;如家&quot;, &quot;fields&quot;:[&quot;brand&quot;,&quot;name&quot;] } } } 11.3 精确查询 代码 @Test void testTerm() throws IOException{ SearchRequest hotelRequest = new SearchRequest(&quot;hotel&quot;); hotelRequest.source().query(QueryBuilders.termQuery(&quot;city&quot;,&quot;上海&quot;)); SearchResponse searchResponse = client.search(hotelRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } @Test void testRange() throws IOException{ SearchRequest hotelRequest = new SearchRequest(&quot;hotel&quot;); hotelRequest.source().query(QueryBuilders.rangeQuery(&quot;price&quot;).gte(150).lte(200)); SearchResponse searchResponse = client.search(hotelRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } DSL GET /hotel._search { &quot;query&quot;:{ &quot;term&quot;:{ &quot;city&quot;:&quot;上海&quot; } } } GET /hotel/_search { &quot;query&quot;:{ &quot;range&quot;:{ &quot;price&quot;:{ &quot;gte&quot;:100, &quot;lte&quot;:150 } } } } 11.4 地理查询 代码 @Test void testGeoDistance() throws IOException{ SearchRequest hotelRequest = new SearchRequest(&quot;hotel&quot;); hotelRequest.source().query(QueryBuilders.geoDistanceQuery(&quot;location&quot;).distance(&quot;15km&quot;).point(31.21,121.5)); SearchResponse searchResponse = client.search(hotelRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } DSL GET /hotel/_search { &quot;query&quot;: { &quot;geo_distance&quot;:{ &quot;distance&quot;:&quot;15km&quot;, &quot;location&quot;:&quot;31.21,121.5&quot; } } } 11.5 复合查询 代码 @Test void testBool() throws IOException{ SearchRequest searchRequest = new SearchRequest(&quot;hotel&quot;); BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); boolQueryBuilder .must(QueryBuilders.termQuery(&quot;city&quot;,&quot;上海&quot;)) .filter(QueryBuilders.rangeQuery(&quot;price&quot;).gte(150).lte(200)); searchRequest.source().query(boolQueryBuilder); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } DSL GET /hotel/_search { &quot;query&quot;:{ &quot;bool&quot;:{ &quot;must&quot;:[ {&quot;term&quot;:{&quot;city&quot;:&quot;上海&quot;}} ], &quot;should&quot;:[], &quot;must_not&quot;:[], &quot;filter&quot;:[ { &quot;range&quot;:{ &quot;price&quot;:{ &quot;gte&quot;:150, &quot;lte&quot;:200 } } } ] } } } 11.6 排序分页 代码 @Test void testPageAndSort() throws IOException{ SearchRequest searchRequest = new SearchRequest(&quot;hotel&quot;); searchRequest.source() .query(QueryBuilders.matchAllQuery()) .from(0) .size(20) .sort(&quot;price&quot;, SortOrder.DESC); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } DSL GET /hotel/_search { &quot;query&quot;:{ &quot;match_all&quot;:{} }, &quot;from&quot;:0, &quot;size&quot;:20, &quot;sort&quot;:[ { &quot;price&quot;:&quot;desc&quot; } ] } 11.7 高亮文档 代码 @Test void testHighlight() throws IOException{ SearchRequest searchRequest = new SearchRequest(&quot;hotel&quot;); HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.field(&quot;name&quot;) .requireFieldMatch(false) .preTags(&quot;&lt;keyword-highlight&gt;&quot;) .postTags(&quot;&lt;/keyword-highlight&gt;&quot;); searchRequest.source() .query(QueryBuilders.matchQuery(&quot;all&quot;,&quot;如家&quot;)) .highlighter(highlightBuilder); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long total = searchHits.getTotalHits().value; System.out.println(&quot;共检索到&quot; + total + &quot;条数据&quot;); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { Map&lt;String, HighlightField&gt; highlightFields = hit.getHighlightFields(); if(highlightFields!=null &amp;&amp; highlightFields.size()&gt;0){ HighlightField highlightField = highlightFields.get(&quot;name&quot;); System.out.println(highlightField.getFragments()[0].toString()); } System.out.println(JSON.toJSONString(hit.getSourceAsMap(), SerializerFeature.PrettyFormat)); } } DSL GET /hotel/_search { &quot;query&quot;:{ &quot;match&quot;:{ &quot;all&quot;:&quot;如家&quot; } }, &quot;highlight&quot;:{ &quot;fields&quot;:{ &quot;name&quot;:{ &quot;pre_tags&quot;:&quot;&lt;em&gt;&quot;, &quot;post_tags&quot;:&quot;&lt;/em&gt;&quot;, &quot;require_field_match&quot;:false } } } } 12. 数据聚合 12.1 聚合的分类 12.2 DSL实现聚合 12.3 RestAPI实现聚合 13. 自动补全 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/DZ1f24lyN/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/DZ1f24lyN/"},{"title":"Docker学习（一）","content":"容器化部署Docker Docker如何解决大型项目依赖关系复杂，不同组件依赖的兼容性问题？ Docker允许开发汇总将应用、依赖、函数库、配置一起打包，形成可以直接安装的镜像 Docker应用运行在容器中，使用沙箱机制，相互隔离 Docker如何解决开发、测试、生产环境有差异的问题？ Docker镜像中包含完整的运行环境，包括系统函数库，仅依赖系统的Linux内核，因此可以在任意Linux操作系统上运行 Docker与虚拟机的区别： 虚拟机是使用Hypervisor技术在操作系统中模拟硬件设备，然后运行另一个操作系统，比如在Windows系统里面运行Ubuntu系统。 特性 Docker 虚拟机 性能 接近原生 性能较差 硬盘占用 一般为MB 一般为GB 启动 秒级 分钟级 1. 概念 1.1 镜像（Image） Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起，称为镜像 1.2 容器（Container） 镜像中的应用程序运行后形成的进程就是容器，只是Docker会给容器做隔离，对外不可见 1.3 DockerHub DockerHub是一个Docker镜像的托管平台，这样的平台称为Docker Registry 国内也有类似于DockerHub的公开服务，比如网易云镜像服务、阿里云镜像库等 1.4 Docker Docker是一个CS架构的程序，由两部分组成 服务端：Docker守护进程，负责处理Docker指令，管理镜像、容器等 客户端：通过命令或RestAPI向Docker服务端发送指令，可以在本地或远程向服务端发送指令 2. 安装Docker 2.1 CentOS7安装Docker CentOS7系统下载地址： http://mirrors.163.com/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso 迅雷下载镜像文件有奇效，IDM平时下载很快，下载镜像开8线程只能跑到1M/s，迅雷直接10M/s 若之前安装过其他版本Docker，通过以下方式完成卸载： yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-lastest-logrotate \\ docker-logrotate \\ docker-selinux \\ docker-engine-selinux \\ docker-engine \\ docker-ce 安装Docker yum install -y yum-utils device-mapper-persistent-data lvm2 更新yum本地镜像源 # 删除原有配置源目录 cd /etc rm -rf yum.repos.d # 新建配置源目录 mkdir yum.repos.d # 下载阿里镜像源 cd yum.repos.d wget https://mirrors.aliyun.com/repo/Centos-7.repo # 更新yum缓存 yum makecache 更新软件源信息 参考：https://developer.aliyun.com/mirror/docker-ce?spm=a2c6h.13651102.0.0.40491b11PUGxwo # step 1: 安装必要的一些系统工具 yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3 # Step 4: 更新并安装Docker-CE yum makecache fast yum -y install docker-ce # Step 4: 开启Docker服务 service docker start 关闭防火墙（为了学习Docker，开发中应该开启指定端口） # 关闭防火墙应用 systemctl stop firewalld # 禁止开机启动防火墙 systemctl disable firewalld # 查看防火墙状态 systemctl status firewalld 配置Docker镜像源 mkdir -p /etc/docker tee /etc/docker/daemon.json &lt;&lt;-'EOF' { &quot;registry-mirrors&quot;: [&quot;https://xm9ypajm.mirror.aliyuncs.com&quot;] } EOF systemctl daemon-reload systemctl restart docker 2.2 Docker基本操作 镜像的命名规范： 镜像名称一般由两部分组成：[respository]:[tag] respository：镜像名称 tag：版本 如果没有指定tag时，默认是latest，代表最新版本的镜像 镜像相关指令 docker images：查看当前所有镜像 docker pull [respository]:[tag]：从镜像仓库中拉取指定镜像 docker save -o [Path/FileName.tar] [respository]:[tag]：将指定镜像打包 docker load -i [Path/FileName.tar]：将打包好的镜像加载到Docker中 docker rmi [respository]:[tag]：移除指定镜像 示例： 从DockerHub中拉取一个nginx镜像并查看 docker pull nginx docker images 将nginx镜像打包到本地 docker save -o ~/nginx.tar nginx:latest 将镜像文件从本地tar包加载 docker load -i ~/nginx.tar docker images 容器相关命令 docker run：创建并运行一个容器 docker pause：暂停运行 docker unpause：继续运行 docker stop：停止运行 docker start：运行 docker ps：查看所有运行容器及状态 docker logs：查看容器运行日志 docker exec：进入容器执行命令 docker rm：删除指定容器 示例： 运行一个nginx容器 docker run --name mn -p 80:80 -d nginx --name：指定容器名称 -p：指定端口映射 -d：后台运行 docker ps 查看指定容器运行日志 docker logs mn 跟踪查看运行日志：docker logs -f mn 进入容器执行命令 docker exec -it mv bash exit：退出容器 停止运行容器 docker stop mn 查看所有容器包括未运行的 docker ps -a 运行容器 docker start mn 删除容器 docker rm：只能删除未运行的容器 docker rm -f mn：强制删除容器，无论是否运行 示例：运行一个持久化存储的redis容器，并通过redis-cli设置num=666 运行容器 docker run --name my-redis -p 6379:6379 -d redis redis-server --appendonly yes 进入容器 docker exec -it my-redis bash 启动redis-cli redis-cli 设置num=666 set num 666 退出redis-cli，退出容器 exit docker exec -it my-redis redis-cli：直接进入容器中启动redis-cli 2.3 数据卷操作 数据卷的作用：将容器与数据分离，解耦合，方便操作容器内数据，保证数据安全 基本语法：docker volume [COMMAND] COMMAND create：创建一个volume inspect：显示一个或多个volume的信息 ls：列出所有的volume prune：删除未使用的volume rm：删除一个或多个指定的volume 挂载数据卷 创建并运行容器时指定数据卷的挂载目录，若数据卷不存在，则自动创建数据卷 docker run \\ --name mn \\ -p 80:80 \\ -v html:/usr/share/nginx/html \\ -d nginx 挂载目录 docker run \\ --name some-mysql \\ -e MYSQL_ROOT_PASSWORD=root \\ -p 3306:3306 \\ -v /tmp/mysql/data:/var/lib/mysql \\ -v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \\ -d mysql:latest 数据卷挂载与目录挂载 数据卷挂载耦合度低，有docker来管理目录，但是目录较深，不好找 目录挂载耦合度高，需要我们自己管理目录，不过目录容易寻找查看 3. 镜像结构 镜像就是将应用程序及其所需要的系统函数库、环境、配置、依赖打包而成的 基础镜像（BaseImage）：应用依赖的系统函数库、环境变量、配置、文件系统等 入口（Entrypoint）：镜像运行入口，一般是程序启动的脚本和参数 层（Layer）：在BaseImage基础上添加安装包、依赖、配置等，每次操作形成新的一层 镜像是分层结构，每一层称一个Layer 3.1 自定义镜像 Dockerfile：一个文本文件，指令的合集，用指令来说明要执行什么操作来构建镜像，每一个指令都会形成一层Layer 指令 说明 示例 FROM 指定BaseImage FROM centos:6 ENV 设置环境变量，可在后面指令使用 ENV key value COPY 拷贝本地文件到镜像的指定目录 COPY ./mysql-5.7.rpm /tmp RUN 执行Linux的shell命令，一般是安装过程的命令 RUN yum install gcc EXPOSE 指定容器运行时监听的端口，是给镜像使用者看的 EXPOSE 8080 ENTRYPOINT 镜像中应用的启动命令，容器运行时调用 ENTRYPOINT java -jar xx.jar # 指定基础镜像 FROM ubuntu:16.04 # 配置环境变量，JDK的安装目录 ENV JAVA_DIR=/usr/local # 拷贝jdk和java项目的包 COPY ./jdk8.tar.gz $JAVA_DIR/ COPY ./docker-demo.jar /tmp/app.jar # 安装JDK RUN cd $JAVA_DIR \\ &amp;&amp; tar -xf ./jdk8.tar.gz \\ &amp;&amp; mv ./jdk1.8.0_144 ./java8 # 配置环境变量 ENV JAVA_HOME=$JAVA_DIR/java8 ENV PATH=$PATH:$JAVA_HOME/bin # 暴露端口 EXPOSE 8090 # 入口，java项目的启动命令 ENTRYPOINT java -jar /tmp/app.jar mkdir -p /tmp/docker-demo 将docker-demo.jar jdk8.tar.gz Dockerfile上传至/tmp/docker-demo docker build -t javaweb:1.0 . docker images docker run --name web -p 8090:8090 -d javaweb:1.0 访问ip:8090/hello/count 我们发现在Dockerfile中构建jdk环境的操作是可复用的，我们应该把构建jdk环境的部分构建一个镜像，这样以后就可以直接使用了，而java:8-alpine帮我们做了这件事 3.2 DockerCompose DockerCompose可以基于Compose文件帮我们快速部署分布式应用，而无需手动一个个创建和运行容器 Compose文件是一个文本文件，通过指令定义集群中的每个容器如何运行 version: &quot;3.9&quot; services: mysql: #指定服务名称 image: mysql:8.0.21 # 指定镜像文件 environment: #设置环境变量 MYSQL_ROOT_PASSWORD: 123456 volumes: #数据卷挂载 - /tmp/mysql/data:/var/lib/mysql - /tmp/mysql/conf/hym.cnf:/etc/mysql/conf.d/hym.cnf web: #指定服务名称 build: . #从当前目录中构建镜像 ports: #设置端口号 - &quot;8090:8090&quot; 书写格式参考规范： https://docs.docker.com/compose/compose-file/compose-file-v3/ https://docs.docker.com/compose/compose-file/compose-file-v2/ 3.2.1 安装DockerCompose 参考：https://docs.docker.com/compose/install/compose-plugin/#installing-compose-on-linux-systems curl -SL https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose docker-compose所在目录/usr/local/bin/docker-compose 给docker-compose添加可执行权限chmod +x docker-compose Base自动补全命令 curl \\ -L https://raw.githubusercontent.com/docker/compose/v2.6.1/contrib/completion/bash/docker-compose \\ -o /etc/bash_completion.d/docker-compose 如果无法访问该地址，则修改本机hosts文件 echo &quot;185.199.108.133 raw.githubusercontent.com&quot; &gt;&gt; /etc/hosts 3.2.2 部署微服务集群 docker run \\ --name my-mysql8 \\ -e MYSQL_ROOT_PASSWORD=root \\ -p 3306:3306 \\ -d mysql:latest \\ --character-set-server=utf8mb4 \\ --collation-server=utf8mb4_unicode_ci FROM java:8-alpine COPY ./app.jar /tmp/app.jar ENTRYPOINT java -jar /tmp/app.jar # docker-comspose配置文件语法版本 version: 3.8 services: nacos: images: nacos/nacos-server environment: MODE: standalone ports: - &quot;8848:8848&quot; mysql: images: mysql:8.0.31 environment: MYSQL_ROOT_PASSWORD: 996748 volumes: - &quot;$PWD/mysql/data:/var/lib/mysql&quot; - &quot;$PWD/mysql/conf:/etc/mysql/conf.d&quot; user-service: build: ./user-service order-service: build: ./order-service gateway: build: ./gateway ports: - &quot;10010:10010&quot; 4. Docker镜像仓库 4.1 配置Docker信任地址 我们的私服采用的是http协议，默认不被Docker信任 # 编辑Docker服务守护进程配置文件 vi /etc/docker/daemon.json # 添加内容 &quot;insecure-registries&quot;:[&quot;http://192.168.96.130:8080&quot;] # 重新加载Docker服务守护进程 systemctl daemon-reload # 重启Docker systemctl restart docker 4.2 使用Docker部署带有图形界面的DockerRegistry version: '3.0' services: registry: image: registry volumes: - ./registry-data:/var/lib/registry ui: image: joxit/docker-registry-ui:1.5-static ports: - 8080:80 environment: - REGISTRY_TITLE=絷缘私有仓库 - REGISTRY_URL=http://registry:5000 depends_on: - registry mkdir /tmp/docker-registry-ui cd /tmp/docker-registry-ui touch docker-compose.yml vim docker-compose.yml docker-compose up -d 4.3 在私有镜像仓库推送/拉取镜像 # 将现有镜像打包成为私有镜像 docker tag nginx:latest 192.168.96.130:8080/nginx:latest # 将私有镜像推送到私有仓库 docker push 192.168.96.130:8080/nginx:latest # 将私有镜像拉取到当前环境 docker pull 192.168.96.130:8080/nginx:latest 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/doBPQq_Z4/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/doBPQq_Z4/"},{"title":"Linux命令学习（十一）","content":"一、部署共享服务器 1. 产生原因：文件传输的服务器可以是主机之间传输文件变得简单，但本质还是传输文件，上传等功能一般不使用，若需要在服务器上传或修改文件一般使用共享文件 2. Samba共享服务程序 2.1 组成 Samba：服务器端软件，提供服务器守护进程、共享程序文档等功能 samba-common：设置文件并提供语法校验功能 samba-client：客户端软件，提供访问时的指令 2.2 Samba服务器配置文件 存储路径：/etc/samba/smb.conf 常用的参数 [global]：全局性配置，不能省略 workgroup=：工作组名 server string=：服务器信息介绍 log file=：定义日志文件存储路径 max log size=：定义日志文件的最大容量（单位为KB） sercurity=：验证方式 passdb backend=：定义用户后台类型 load printers=yes：设置是否共享打印机 cups options=raw：设置打印机的选项 [homes]：本地参数，homes可以修改为Windows显示的共享目录名 comment=：描述信息，可以省略 path=：共享目录的绝对路径，不能省略 public=no：关闭所有人可见 browsable=yse：定义是否在windows工作组下看的共享目录 writeable=yes：允许写入 3. 部署共享服务 准备工作 关闭SELinux：setenforce 0 关闭防火墙：systemctl stop firewalld 安装samba：yum install -y samba 查看本机IP：ip addr 创建新账户 useradd smbtest passwd smbtest 使用samba的pdbedit命令添加新账户 格式：pdbedit -a -u smbtest 注意：samba服务要求账户必须事先建立，为普通账户，密码为新密码，不能使用原有的账户登录密码 创建共享目录 mkdir /smbdata 修改共享目录权限：chown -Rf smbtest:smbtest /smbdata 配置samba配置文件 备份主配置文件：mv smb.conf smb.conf.back 新建主配置文件：vim smb.conf 输入以下内容： [global] workgroup = SAMBA server string = Samba Server v%v log file = /var/log/samba/log.%m 定义日志文件的存放位置与名称，参数%m为来访的主机名 max log size = 50 定义日志文件的最大容量为50KB security = user 安全验证的方式，总共有4种 load printers = yes 设置在Samba服务启动时是否共享打印机设备 cups options = raw 打印机选项 [smbdata] path = /smbdata 共享目录位置 public = no 等同于 guest ok = no 是否所有人可见，等同于&quot;public&quot;参数。 browseable = yes 该选项设置共享目录在网上邻居中是否显示，设置为no时相当于隐藏共享目录，该选项是专门用于windows平台 writable = yes 定义是否可以执行写入操作，与“read only”相反 security所有参数： share：来访主机无需验证口令；比较方便，但安全性很差 user：需验证来访主机提供的口令后才可以访问；提升了安全性 server：使用独立的远程主机验证来访主机提供的口令（集中管理账户） domain：使用域控制器进行身份验证 启动服务：systemctl start smb 开机启动：systemctl enable smb 测试：在Windows的运行输入：\\\\服务器IP，也可以在文件夹地址栏中输入 二、 使用LNMP环境部署Discuz论坛 1. LNMP：Linux+Nginx+MySQL（Mariadb）+PHP 2. 准备工作 2.1 关闭SELinux：setenforce 0 2.2 关闭防火墙：systemctl stop firewalld 2.3 查看本机IP地址：ip addr 3. 安装必要软件 3.1 安装扩展源：yum install -y epel-release 3.2 安装编译器：yum install -y gcc gcc-c++ 3.3 安装解压缩软件：yum install -y zlib* 3.4 安装PCRE软件：yum install -y pcre* 3.5 安装加密软件：yum install -y openssl* 3.6 安装PHP：yum install -y php 3.7 安装数据库：yum install -y mariadb-server 3.8 安装PHP支持软件：yum install -y php-fpm php-mysql 3.9 安装Nginx 下载源码包：wget http://nginx.org/download/nginx-1.19.1.tar.gz 解压缩：tar xzvf nginx-1.19.1.tar.gz 进入安装目录：cd nginx-1.19.1 编译安装：./configure &amp;&amp; make &amp;&amp; make install 3.10 在Window下载Discuz论坛源码包（必须为UTF-8格式），通过WinSCP上传到Linux家目录中 4. 配置Nginx 4.1 启动 Nginx启动的程序默认路径：/usr/local/nginx/sbin 进入目录：cd /usr/local/nginx/sbin 测试：在浏览器中输入 http://服务器IP地址 4.2 将Discuz论坛的源码文件中upload目录拷贝到nginx默认网页存储路径 nginx默认网页存储路径：/usr/local/nginx/html 注意：该目录中的index.html就是测试网页的源文件 将Discuz论坛的upload目录拷贝到/usr/local/nginx/html中并赋予权限 cd ~ ls cd Discuz_X3.4_SC_UTF8_20191201/ ls cp -rf upload /usr/local/nginx/html ls chmod -Rf 777 upload rm -f index.html 4.3 修改nginx配置文件 路径：/usr/local/nginx/conf/nginx.conf 编辑：vim /usr/local/nginx/conf/nginx.conf 定位第36行，确认监听端口为80 定位第44行，将root后边的html删除，替换为：/usr/local/nginx/html/upload，该动作为设置默认网页目录路径 定位第45行，添加index.php，开启php文件识别 定位65-71行，删除前导#号，开启PHP支持，将66行的html修改为 /usr/local/nginx/html/upload 定位第69行，将 /scripts 改为 $document_root 保存退出，重启Nginx cd /usr/local/nginx/sbin/ ./nginx -s reload 5. 配置mariadb数据库 5.1 systemctl start mariadb 5.2 mariadb数据库初始化 执行初始化的命令：mysql_secure_installation 进入初始化向导 Enter current password for root (enter for none)：是否为root账户设置密码，回车 Set root password? [Y/n] ：输入y回车，输入密码 Remove anonymous users? [Y/n] ：是否删除匿名账户，输入y回车 Disallow root login remotely? [Y/n] ：是否禁止root管理员从远程登录，输入y回车 Remove test database and access to it? [Y/n] ：是否删除test数据库并取消其访问权限，输入y回车 Reload privilege tables now? [Y/n] ：是否刷新授权列表，让初始化后的设定生效，输入y回车 重启数据库： systemctl restart mariadb 6. 启动php-fpm服务：systemctl start php-fpm 7. 安装Discuz论坛服务 7.1 在Windows的浏览器中数额如http://服务器IP地址进入向导，按提示操作 7.2 注意：如果出现红叉×，一般是权限问题，或者软件未安装 7.3 安装数据库 设置密码：mariadb数据库初始化时设置的root密码 设置论坛管理员用户名及密码 8. 测试：Windows打开浏览器输入服务器IP地址进行测试 9. LNMP一键安装包部署论坛 9.1 准备工作 关闭SELinux 关闭防火墙 查看IP地址 使用winscp将windows中的LNMP一键安装包上传到Linux 检查yum源是否可用：yum makecache 解压缩安装包：tar xzvf lnmp1.7-full.tar.gz 9.2 安装LNMP一键安装包： cd lnmp1.7-full 执行安装脚本 ./install.sh Enter your choice (1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or 0)：选择安装数据库版本 Please setup root password of MySQL. Please enter: 输入数据库管理员的密码 Do you want to enable or disable the InnoDB Storage Engine? Default enable,Enter your choice [Y/n]: 是否启用MySQL数据库引擎 Enter your choice (1, 2, 3, 4, 5, 6, 7, 8, 9, 10): 选择php的版本，回车即可 Enter your choice (1, 2 or 3)：是否安装内存优化，不安装回车即可 按任意键开始安装 配置Nginx 注意：LNMP一键安装包的Nginx是经过优化配置的，与默认的Nginx配置文件内容有所不同 网站目录：/home/wwwroot/default 将Discuz的upload目录拷贝到/home/wwwroot/default目录中并赋予权限 chown www:www -R /home/wwwroot/default/upload 修改Nginx.conf的配置文件 vim /usr/local/nginx/conf/nginx.conf 定位第69行，修改为root /home/wwwroot/default/upload 重启Nginx cd /usr/local/nginx/sbin ./nginx -s reload 测试：地址栏输入服务器IP地址进行测试 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/HTHKqujWe/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/HTHKqujWe/"},{"title":"Linux命令学习（十）","content":"部署文件服务器 1. 文件服务器 1.1 一般用于文件传输协议（FTP），用于FTP客户端与服务端的文件共享、上传下载 1.2 结构：客户机/服务器（C/S架构） 1.3 端口： 20：用于数据传输 21：用于传输命令 1.4 特点：FTP服务器普遍部署在内网，一般使用下载功能较多，容易搭建，方便管理 1.5 结构图： 1.6 FTP协议工作模式 主动模式：FTP的服务器主动向客户机发出连接请求 被动模式：FTP服务器等待客户机发出连接请求（默认模式） 1.7 vsftpd服务程序 1.7.1 vsftpd：非常安全的FTP守护进程 1.7.2 vsftpd是一款运行在Linux系统上的FTP服务程序，具有完全开源、免费、高安全性、高可靠性、高传输速度、支持多种服务模式的FTP服务器端软件 1.7.3 工作模式： 匿名开放模式：任何人都可以无需密码验证而直接登录FTP服务器，方便简单但极不安全 本地用户模式：通过Linux系统本地的普通账户和密码进行认证登录，相对安全，若本地系统账户信息泄露或被破解，则FTP服务器就可顺利登录，从而控制整个服务器 虚拟用户模式：单独的建立用户数据库文件，虚拟出账户及密码，用于验证登录，实际账户信息不存在，仅供FTP服务器进行认证使用，有效地降低了破坏范围 1.7.4 访问方式 服务器端：vsftp 客户机端： 命令行：ftp 或 lftp 命令访问 Windows：浏览器或目录界面 1.7.5 安装vsftpd 服务端格式：yum install -y vsftpd 客户端格式：yum install -y 1.7.6 vsftpd服务软件的配置文件：/etc/vsftpd/vsftpd.conf ① 部署匿名开放模式的服务器 准备工作 关闭 SELINUX 关闭防火墙：systemctl stop firewalld （或加入防火墙白名单：firewall-cmd --permanent --zone=public --add-service=ftp） 重启防火墙：firewall-cmd --reload 编辑配置 vsftpd.conf 文件，实现匿名模式服务器 编辑：vim /etc/vsftpd/vsftpd.conf 匿名模式涉及的参数： anonymous_enable=YES（允许匿名访问） write_enable=YES（设置可写权限） anon_umask=022（需添加，设置匿名账户上传文件的umask值） anon_upload_enable=YES（需去掉#，允许匿名账户上传文件） anon_mkdir_write_enable=YES（需去掉#，允许匿名账户创建目录） anon_other_write_enable=YES（需添加，允许匿名账户修改目录名或删除目录） 设置vsftpd默认工作目录的权限 默认目录：/var/ftp/pub 对该目录增加ftp账户对应的权限：chown -Rf ftp /var/ftp/pub 启动服务 启动：systemctl start vsftpd 设置开机启动：systemctl enable vsftpd 测试 Windows：在目录界面或浏览器界面的地址栏输入ftp://ip地址 Linux： ftp软件： 命令格式为： ftp 对方IP地址 输入用户名anonymous，密码回车，cd pub后进行ls浏览和尝试mkdir创建目录，输入exit退出 lftp软件： 命令格式为：lftp 对方IP地址 cd pub后进行ls浏览和尝试mkdir创建目录，输入 “ get 文件名 ” 进行上传，输入exit退出 umask参数 umask值是Unix系统的概念，决定目录及文件被创建时的初始权限 在vsftpd的服务器中的目录初始权限最高为777，文件由于不能设置为可执行，则最高权限为666 umask值本质是一种补码 目录实际权限计算方式：777-umask，例如umask=022，目录初始权限为777-022即755 文件实际权限计算方式：666-umask，例如umask=022，文件初始权限为666-022即644 ② 部署本地用户模式的ftp服务器 本地模式就是通过Linux的普通账户进行登录 准备工作：同匿名的准备工作 关闭 SELINUX：setenforce 0 关闭防火墙：systemctl stop firewalld （或加入防火墙白名单：firewall-cmd --permanent --zone=public --add-service=ftp） 重启防火墙：firewall-cmd --reload 配置vsftpd.conf文件实现本地模式 打开文件：vim /etc/vsftpd/vsftpd.conf 本地模式涉及的参数： anonymous_enable=NO（关闭匿名模式） local_enable=YES（开启本地模式） write_enable=YES（设置可写权限） local_umask=022（设置本地模式账户umask值） userlist_enable=YES（启用 “ 禁止用户名单 ”） userlist_deny=YES（需添加，启用用户作用名单） 注意：userlist_enable和userlist_deny都为YES时，/etc/vsftpd/user_list文件为 “黑名单”，即文件中的账户都不能登录 注意：userlist_enable=YES和userlist_deny=NO时，则/etc/vsftpd/user_list文件为 “白名单”，即只有该文件中的用户可以登录 启动服务：systemctl start vsftpd 设置开机启动：systemctl enable vsftpd 上传文件默认目录：/home/普通账户名/ 测试 Windows： 在目录界面或浏览器界面的地址栏输入ftp://ip地址，输入普通账户名及密码进行登录操作 Linux： ftp软件： 命令格式为： ftp 对方IP地址 输入用户名ZHIYUAN，密码回车登录，cd pub后进行ls浏览和尝试mkdir创建目录，输入exit退出 lftp软件： 命令格式为：lftp 对方IP地址 -u 普通用户名 输入对应用户的密码，回车登录，cd pub后进行ls浏览和尝试mkdir创建目录，输入exit退出 限制本地用户只能在本地家目录中访问 一般用户登录后访问的默认目录为/home/账户名/，但通过切换目录可以访问提权到其上级目录，这是相当危险的行为，需要做如下限制 设置： chroot_local_user=YES（去掉#，开启限制用户工作的目录） allow_writeable_chroot=YES（需添加，设置为不可跳出主目录后允许登录，且主目录可写） 注意：设置完毕后必须重启vsftpd服务：systemctl restart vsftpd 测试： ftp 192.168.0.119 输入账户名及密码 尝试cd /etc cd .. ③ 部署虚拟用户模式的ftp服务器 准备工作 关闭SELINUX ：setenforce 0 关闭防火墙：systemctl stop firewalld （或加入防火墙白名单：firewall-cmd --permanent --zone=public --add-service=ftp） 若添加白名单请重启防火墙：firewall-cmd --reload 设置工作默认权限：chown -Rf ftp /var/ftp/pub 安装必要软件： 安装vsftpd ：yum install -y vsftpd 安装yum扩展源：yum install -y epel-release 安装db4数据库软件：yum install -y db4* 创建FTP认证的数据库软件 作用：存储允许登录的账户名及密码 格式：奇数行为账户名，偶数行为密码 创建位置：/etc/vsftpd ftpuser1 123456 ftpuser2 654321 过程 vim /etc/vsftpd/vuser.list 输入账户及密码，注意：奇数行为账户名，偶数行为密码 由于vuser.list是明文，不安全，且不符合vsftpd程序加载文件的格式，需要使用db_load命令进行hash加密，并转为数据库文件，最后删除原始明文 加密：db_load -T -t hash -f /etc/vsftpd/vuser.list /etc/vsftpd/vuser.db -T：将文本文件转译载入数据库 -t hash：使用hash算法加密 -f：指定包含用户名和密码的文本文件 设置权限：chmod 600 vuser.db 删除原始明文：rm -f vuser.list 创建虚拟账户映射的账户及家目录 作用：虚拟账户是没有家目录的，权限也未知，则需要虚拟账户登录后映射到本地实际账户的家目录中，其权限也自动归属于该本地账户 过程： useradd -d /var/ftpuser -s /sbin/nologin vfox 设置家目录权限：chmod -Rf 755 /var/ftpuser 创建PAM文件进行映射 PAM：可拔插认证模块，是一种认证机制，系统管理员可以通过调整PAM就轻易修改对应的认证方式，从而不需要修改应用程序 过程： vim /etc/pam.d/vsftpd.vu 输入以下两行进行映射 auth required pam_userdb.so db=/etc/vsftpd/vuser account required pam_userdb.so db=/etc/vsftpd/vuser （注意：vuser不需要加扩展名db） 配置 vsftpd.conf 文件涉及参数 anonymous_enable=NO（关闭匿名模式） local_enable=YES（开启本地账户） guest_enable=YES（需添加，开启虚拟账户模式） guest_username=vfox（需添加指定映射的账户名） pam_service_name=vsftpd.vu（指定pam文件） allow_writeable_chroot=YES（需添加，设置为不可跳出主目录且允许登录可写） 重启服务：systemctl restart vsftpd 例：设置ftpuser1可以上传、创建、修改、删除，ftpuser2只能查看 原则：通过创建权限文件实现 过程： mkdir -p /etc/vsftpd/vsdir cd /etc/vsftpd/vsdir vim ftpuser1 输入以下自定义功能参数： anon_upload_enable=YES anon_mkdir_write_enable=YES anon_other_write_enable=YES 在 vsftpd.conf 配置文件中设置账户访问权限配置文件的存储目录 vim /etc/vsftpd/vsftpd.conf user_config_dir=/etc/vsftpd/vsdir 重启服务：systemctl restart vsftpd 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/RptKbpSRd/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/RptKbpSRd/"},{"title":"Linux命令学习（九）","content":"部署静态网站 1. web网站组成 网站服务程序：Apache、Nginx、IIS7 服务器或云存储空间 Web页面 2. 快速安装Apache服务程序 安装Apache：yum install -y httpd 关闭SELinux：setenforce 0 关闭防火墙：systemctl stop firewalld（将httpd加入到防火墙白名单） 启动httpd服务：systemctl start httpd 设置开机启动：systemctl enable httpd 3. Apache配置文件分析 位置： 配置文件路径：/etc/httpd/conf/httpd.conf 服务目录：/etc/httpd 默认访问网站的存储目录：/var/www/html 网站访问日志路径：/var/log/httpd/access_log 错误日志：/var/log/httpd/error.log 主配置文件分析 分类： 注释内容 全局配置 区域配置 需要关注的关键参数 ServerRoot：服务程序主目录 ServerAdmin：管理员邮箱 User：运行服务的用户 Group：运行服务的工作组 ServerName：网站域名 DocumentRoot：网站数据目录 Listen：监听IP地址与端口号 Include：需要加载的其他文件 Timeout：网页超时时间，单位毫秒ms 4. 快速部署个人网站 安装配置服务 安装Apache：yum install -y httpd 关闭SELinux：setenforce 0 关闭防火墙：systemctl stop firewalld（将httpd加入到防火墙白名单） 启动httpd服务：systemctl start httpd 设置开机启动：systemctl enable httpd 创建存储网页的源文件的目录：mkdir /web1 将网页源文件上传到该目录winscp 启动winscp 站点输入linux系统的IP地址 用户输入root及密码 保存站点并登录，第一次需要验证密钥，点击是即可 登录成功后左侧为Window，右侧为Linux 通过鼠标拖拽文件即可 修改httpd配置文件 编辑主配置文件：vim /etc/httpd/conf/httpd.conf 定位第119行将 /var/www/html 修改为 /web1/网页目录名 定位第124行将 /var/www 修改为 /web1/网页目录名 保存并退出 重启服务：systemctl restart httpd 测试：在Windows端打开浏览器输入Linux系统的IP地址 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/7dxxE7ipp/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/7dxxE7ipp/"},{"title":"Linux命令学习（八）","content":"Arch-Linux安装部署（知识小结） 1. Arch-Linux系统： 基于X86_64位架构制作的一种特别发行版，系统全部由开源软件组成，帮助文档丰富，全部由社区进行管理和维护，其镜像只提供简单的系统组件，需要全程联网才可安装部署 2. 创建Arch-Linux的虚拟机 文件菜单 -&gt; 新建虚拟机 -&gt; 稍后安装系统 -&gt;选择版本为Linux、其他版本、Linux5.x或更高版本内核64位 -&gt; 命名为Arch-Linux -&gt; 更改存储路径 -&gt; 磁盘容量8G ，单个文件 -&gt;自定义硬件（新CD/DVD中选择之前下载好的镜像文件） 3. 启动虚拟机 进入引导界面 -&gt; 选择第一项 -&gt; 自动进入命令行 4. 部署系统 （1）检查网络连通性 ​ 获取地址：ip addr ​ 测试：ping www.baidu.com （2）创建分区 ​ 方法：fdisk /dev/sda ​ 至少两个分区： ​ swap分区：512M ​ 根分区：剩余所有 （3）启用swap分区 ​ 格式化swap分区：mkswap /dev/sda1 ​ 启用：swapon /dev/sda1 ​ 查看：free -h （4）格式化根分区：mkfs.ext4 /dev/sda2 （5）挂载根分区：mount /dev/sda2 /mnt （6）修改下载源，防止下载速度过慢 ​ 进入下载源配置目录：cd /etc/pacman.d ​ 将所有行注释掉 ​ 定位第一行增加阿里源的地址信息：Server = http://mirrors.aliyun.com/archlinux/repo/os/repo/os/repo/os/arch （7）下载必要软件： ​ pacstrap /mnt base ​ pacstrap /mnt linux ​ pacstrap /mnt linux-firmware ​ pacstrap /mnt dhcpcd ​ pacstrap /mnt vim ​ pacstrap /mnt openssh ​ pacstrap /mnt man ​ pacstrap /mnt net-tools ​ pacstrap /mnt pacman （8）生成开机启动文件 ​ 写入开机启动内容：genfstab -U /mnt &gt;&gt; /mnt/etc/fstab ​ 浏览：cat /mnt/etc/fstab （9）切换根目录：arch-chroot /mnt （10）设置root密码：passwd root （11）设置开机启动：systemctl enable dhcpcd （12）安装并配置grub2引导文件： ​ 安装：pacman -S grub ​ 部署到硬盘：grub-install /dev/sda ​ 写入引导：grub-mkconfig -o /boot/grub/grub.cfg ​ 退出当前系统：exit ​ 重启：reboot ​ 历经千辛万苦，终于成功启动 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/m9qF680b6/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/m9qF680b6/"},{"title":"Linux命令学习（七）","content":"一、 1. 软件下载及安装方式 1.1 源码安装 1.1.1 原理：将软件原始代码（C/C++）下载下来，将其编译后安装使用 1.1.2 源码文件：编写者使用C或C++等语言编写的原始代码文件 1.1.3 源码文件使用.tar.gz或.tar.bz2打包成压缩文件 1.1.4 源码安装过程： ① 下载解压源码压缩包 ② 分析软件安装环境 ③ 编译软件 ④ 安装软件 1.1.5 源码安装特点： ① 源码包可移植性好，与待安装软件的工作环境依赖性不大 ② 由于有编译过程，其代码运行效率高，是对当前使用者工作环境的 “量体裁衣” ③ 安装功能可以定制，可以选择需要的功能进行安装使用 ④ 卸载时，只需删除对应安装目录即可，没有Windows的注册表概念 ⑤ 安装过程繁琐 1.1.6 源码下载解压缩分析 ① 下载：wget URL ② 解压缩 .tar.gz格式：tar xzvf 压缩包名称 .tar.bz2格式：tar xjvf 压缩包名称 1.1.7 例1：下载安装Apache源码包 ① 必须先安装编译器：yum install -y gcc gcc-c++ ② 下载Apache源码包： wget https://mirrors.tuna.tsinghua.edu.cn/apache//httpd/httpd-2.4.46.tar.gz ③ 解压缩：tar xzvf httpd-2.4.46.tar.gz ④ 进入安装目录：cd httpd-2.4.46 ⑤ 源码包安装文件分析及安装环境分析 意义：目录中有configure可执行文件，用于对安装环境进行测试、检测所需库文件、工具文件是否存在、检测依赖性关系、编译器和汇编器与连接器检查 执行方式：./configure ⑥ 接上例： 检查环境：./configure 提示缺少apr文件，则安装apr依赖文件：yum install -y apr* 重新检查：./configure 提示缺少pcre文件，在提供的网站下载 cd ~ wget https://ftp.pcre.org/pub/pcre/pcre-8.00.tar.gz 解压缩pcre后，检查pcre安装环境并安装 解压缩：tar xzvf pcre-8.00.tar.gz 进入目录：cd pcre-8.00/ 检查pcre安装环境：./configure 编译：make 安装：make install 返回上一级目录：cd .. 进入apache目录：cd httpd-2.4.46/ 再次检查apache安装环境：./configure 编译：make 安装：make install ⑦ 编译软件 作用：对./configure命令生成的makefile文件记录的信息进行编译，生成二进制安装文件、 格式：make 注意：make过程中若报错，需要查看提示信息，根据信息修改，一般为缺少头文件等问题 ⑧ 安装软件 根据make命令生成的二进制安装文件进行安装，默认安装目录：/user/local或/user/local/bin目录 方法：make install ⑨ 例2：源码安装python3编译器 wget https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz 解压缩：tar xzvf Python-3.9.0.tgz 进入目录：cd Python-3.9.0 检查安装环境：./configure 安装：yum install -y zlib* 编译：make 安装：make install ⑩ 注意： 可以使用 echo $? 检查上一步动作是否正常返回，正常返回0 遇到yum进程被占用：kill 进程ID 源码包解压缩后一般会有README和INSTALL文件，一般记录安装注意事项及安装方式 检查安装环境时，可以指定安装目录，格式：./configure -prefilx=目录路径 检查环境与编译和安装可以使用 “并且运算符”（&amp;&amp;）进行连接，按顺序执行：./configure &amp;&amp; make &amp;&amp; make install 2. RPM安装 2.1 产生原因：软件制作者在其工作环境下将软件编译完毕后再发布，以此减少安装编译时间，但要求软件环境与编译时环境要相同 2.2 RPM：红冒软件管理器 2.3 RPM安装软件的流程 2.3.1 读取RPM软件包头部数据，进行软件依赖性检查 2.3.2 若满足条件则安装软件，否则失败要求 2.4 要求 2.4.1 软件安装环境要与打包软件环境一致 2.4.2 必须满足软件依赖性 2.4.3 软件卸载时必须小心，最底层软件不能先卸载 2.4.4 RPM软件包名称解释： 例：zip-3.0-11.el7.x86_64.rpm 软件名 - 版本号 - 编译次数 - 适用平台 noarch：无平台限制 I386：32位运行平台 x86_64 及 AMD64：64位平台 el7：红帽版本 2.4.5 RPM软件安装方式 格式：rpm -ivh 软件包名称.rpm 参数： -i：安装软件 -v：查看安装信息 -h：查看安装进度 --nodeps：发生安装依赖时，进行强制安装，但安装后可能无法使用 --force：安装时提示已安装，则进行覆盖安装 例1：适用RPM方式安装Nginx 下载：wget http://nginx.org/packages/centos/7/x86_64/RPMS/nginx-1.8.0-1.el7.ngx.x86_64.rpm 安装：rpm -ivh nginx-1.8.0-1.el7.ngx.x86_64.rpm 例2：手动安装gcc编译器 rpm -e gcc rpm -q gcc 2.4.6 RPM查询及其他命令 rpm -q 软件包全名：查询指定软件是否安装 rpm -qa ：查询所有已安装软件 rpm -qi 软件名：显示已安装软件的详细信息 rpm -qc 软件名：显示软件的配置文件 rpm -qd 软件名：显示软件帮助文件 rpm -ql 软件名：列出软件所有文件与目录 rpm -e 软件名：卸载软件（卸载时注意依赖性，从最上层软件开始卸载） 3. yum软件安装方式 3.1 机制：将编译好的软件放置在yum服务器中，并将这些软件的依赖性信息记录下来形成表格组成数据库，用户安装软件时会向网络中的yum服务器下载对应依赖数据库并与本机rpm数据库进行比较，最后一同下载全部软件进行安装，自动解决依赖性问题 3.2 yum软件仓库：记录软件依赖性信息的数据库 3.3 修改yum源 作用：Centos系统默认的yum仓库网址服务器位于国外，下载速度过慢，应将其修改为国内的yum仓库地址 方法： 删除yum配置文件目录：rm -rf /etc/yum.repos.d 重新建立该目录：mkdir /etc/yum.repos.d 进入目录：cd /etc/yum.repos.d 下载网易开源镜像站配置文件：wget http://mirrors.163.com/.help/CentOS7-Base-163.repo 清除yum缓存：yum clean all 重新建立yum缓存：yum makecache 3.4 安装软件：yum install -y 软件列表 3.5 升级软件：yum update -y 软件列表 3.6 卸载软件：yum remove 软件名 3.7 例：yum install http tomcat -y 3.8 安装带有URL的rpm包： yum install https://wdl1.cache.wps.cn/wps/download/ep/Linux2019/9662/wps-office-11.1.0.9662-1.x86_64.rpm 3.9 yum配置文件分析 存储路径：/etc/yum.repos.d/CentOS-Base.repo 分析： [base]：表示yum软件仓库名，[]不能省略，内容可以自行设置 name：描述软件仓库的意义，可省略 baseurl：yum仓库服务器地址，非常重要 enabled=1：是否启用，1启用，0不启用 gpgcheck=1：检查数字签名，0不检查 gpgkey：检查数字签名的公钥，gpgcheck=0时可省略 使用本地光盘制作yum仓库 作用：当本地无网络时，安装软件可以使用本地DVD光盘作为软件源 步骤： 挂载光盘：mount /dev/sr0 /media ​ cd /etc/yum.repos.d ​ mv CentOS7-Base-163.repo CentOS7-Base-163.repo.back 新建文件：vim DVD.repo 输入以下内容 保存并退出 清除缓存：yum clean all 新建缓存：yum makecache 查看是否启用：yum repolist all 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/vLqK58UZf/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/vLqK58UZf/"},{"title":"Linux命令学习（六）","content":"一、vim编辑器的使用 1. vim是vi编辑器的升级版 2. vim的三种模式 2.1 命令模式：又称为浏览模式，可以控制光标移动、删除、复制、粘贴等工作 2.2 编辑模式：插入模式，可以进行正文编辑 2.3 末行模式：保存、退出、查询等工作 3. vim进入顺序及方法 3.1 默认进入：命令模式 3.2 命令模式下点击键盘 “i” 键进入编辑模式，点击ESC键回到命令模式 3.3 命令模式下点击冒号进入末行模式，末行模式下点击ESC键回到命令模式 3.4 顺序图： 3.5 编辑模式和末行模式不可以相互进入 4. 创建文件的三种方式 touch f1 vim f2 echo “China” &gt; f3 5. 命令模式下快捷键 名称 作用 yy 复制本行 5yy 复制从光标开始的下5行 dd 剪切（删除）本行 3dd 剪切（删除）从光标开始的下3行 p 将之前剪切或复制的内容进行粘贴 0 定位本行行首 $ 定位本行行尾 GG 定位文本最后一行 gg 定位文本第一行 （ 定位到段落首部 ） 定位到段落尾部 数字G 定位第几行 数字→ 定位第几列 ZZ 保存并退出 u 撤销上一步动作 6. 末行模式下的快捷键 名称 作用 :wq 保存并退出 :w 新文件存储路径 另存为 :q 不保存退出 :q! 强制退出 :e! 将文件恢复到初始状态（撤销所有操作） :set nu 显示行号 :数字 跳转到某行 :/字符串 检索字符串 检索后点击n 依次定位所有检索关键字 :命令 打开文件时执行其他命令 7. 编辑系统主机名： vim /etc/hostname 8. 文本中所有行全部注释方法 开启大写状态 点击Ctrl+V 使用方向键由上到下选择所有行 点击 “ I ”（为大写I） 输入 “#” 点击ESC键 9. 编辑网卡配置文件，设置静态IP vim /etc/sysconfig/network-scripts/ifcfg-ens33 将BOOTPROTO=dhcp修改为BOOTPROTO=static 增加以下几行 IPADDR=192.168.0.10（IP地址） NETMASK=255.255.255.0（子网掩码） GATEWAY=192.168.0.1（网关） DNS1=114.114.114.114（域名解析服务器地址） 保存并退出 重启网卡：service network start 测试：ping www.baidu.com 10. vim的异常处理 例：vim test ​ 点击 i 键输入 “china” ​ 保存并退出 ​ vim test ​ 点击 i 键输入 “china” ​ 点击ESC键 ​ 按下Ctrl+Z键强制中断，再次打开vim会有异常报错 异常报错的原因： 原因1：编辑过程中，异常中断（断电等） 原因2：编辑过程中，有他人或程序正在或尝试打开编辑文件 解决方法： 点击R键回车修复文件 继续编辑后保存并退出 删除缓存文件： ls -a rm -f .test.swp 再次打开无报错 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/bjTsu6fKf/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/bjTsu6fKf/"},{"title":"Linux命令学习（五）","content":"一、RAID与LVM 1. RAID 作用：RAID独立磁盘冗余阵列，使用多块硬盘，组成大容量的硬盘组，将数据分成多个片段同时进行读写，以提高读写速度，并通过冗余备份实现数据安全性 RAID0 原理：将至少两块硬盘通过硬件或软件方式 “串联” 起来，组成一个大的硬盘卷组，将数据一次写入各个硬盘中 图： 特点 优点：数据同步传输，读取/写入分开，性能提升较大 缺点：任意一块硬盘损坏，整个系统数据无法使用 硬盘使用率100%，至少2块硬盘才可组建 RAID1 原理：把至少两块硬盘 “并联” 起来，写入数据时，数据同时写入到另一硬盘中 图： 特点 优点：使用硬盘镜像功能，提高数据安全性 缺点：硬盘利用率50%，使用两块硬盘，一块存储，一块备份，成本较大 RAID5 产生原因：兼顾读写速度及数据安全，是一种折中方式 原理：至少使用三块硬盘，将数据分化存储到不同硬盘中，硬盘中必须存储其他硬盘的校验数据 图： 特点： 优点：兼顾性能，通过 “奇偶校验” 来替代 “镜像备份” 缺点：硬盘数据安全性较低，硬盘使用率（n-1）*n，硬盘数据出现问题会自动校验恢复 RAID10（主流） 本质：RAID1+RAID0 原理：至少需要四块硬盘，先制作两两硬盘的RAID1阵列，以保证数据安全性，在两两制作RAID0以提高读写性能 mdadm命令 作用：管理系统中的RAID磁盘阵列 格式：mdadm -模式参数 RAID设备文件名 -性能参数 磁盘成员文件名 参数： -C：创建RAID -D：查看RAID详细信息 -Q：查看摘要信息 -S：停止RAID工作 -a：检测设备名称，添加新硬盘 -n：指定硬盘数量 -l：指定RAID级别 -v：显示过程 -f：模拟设备损坏 -r：移除RAID中的硬盘设备 例：创建RAID10 mdadm -Cv /dev/md0 -a yes -n 4 -l 10 /dev/sdb /dev/sdc /dev/sdd /dev/sde 分析 -Cv：创建RAID并显示过程 /dev/md0：创建的RAID设备文件名 -a yes：检测RAID设备名并确认 -n 4：参与制作的硬盘个数 -l 10：RAID级别为RAID10 最后为参与制作RAID10的硬盘文件名 格式化：mkfs.xfs /dev/md0 建立挂载点目录：mkdir -p /RAID 挂载RAID设备：mount /dev/md0 /RAID 查看RAID信息：mdadm -D /dev/md0 开机挂载：vim /etc/fstab 添加新行：/dev/md0 /RAID xfs defaults 0 0 reboot重启 df -h查看信息 RAID10 制作小结 mdadm 命令制作 mdadm -Cv /dev/md0 -a yes -n 4 -l 10 /dev/sdb /dev/sdc /dev/sdd /dev/sde mkfs.xfs 格式化 建立挂载点目录 mount 挂载 vim /etc/fstab 开机挂载 mdadm -D 查看信息 解散当前RAID 注意：在生产环境中若要取消RAID，必须先进行数据备份，否则已存在数据无法使用 第一步：备份数据 第二步：取消挂载状态 umount /dev/md0 第三步：删除开机挂载信息 第四步：停止 RAID10 mdadm -S /dev/md0 第五步：重启 reboot RAID损坏修复功能 模拟某个硬盘损坏：mdadm /dev/md0 -f /dev/sdb 查看信息：mdadm -D /dev/md0 卸载RAID：umount /RAID 添加新硬盘：mdadm /dev/md0 -a /dev/sdf 查看信息：mdadm -D /dev/md0 RAID备份技术 原理：在RAID10中添加一块大容量硬盘，通过新硬盘进行备份数据，防止2块硬盘同时故障，若某块硬盘故障，备份盘会同步恢复，无需人工干预 例：创建RAID10+备份盘阵列系统 创建RAID10+备份盘： mdadm -Cv /dev/md1 -a yes -n 4 -l 10 -x 1 /dev/sdb /dev/sdc /dev/sdd /dev/sde /dev/sdf 注意：-x 1表示备份盘 ，且为1块硬盘，一般命令的最后一个硬盘为备份盘 查看信息：mdadm -D /dev/md1 格式化：mkfs.ext4 /dev/md1 模拟硬盘损坏：mdadm /dev/md1 -f /dev/sdb 注意：/dev/sdf会自动补上，并且自动同步数据 2. LVM逻辑卷管理器 产生原因：硬盘分区及RAID部署后再修改空间大小时就十分不方便，甚至数据会丢失，用户需要动态灵活调整硬盘分区大小时，需要通过LVM技术实现 原理：LVM在磁盘分区与文件系统之间添加一个逻辑层，以提供一个抽象的卷组，使得管理者可以忽略底层磁盘布局，从而实现对分区的灵活调整 图： 分析 PE：数据基本块，默认为4MB大小 PV：物理卷，由整个硬盘或分区组成 VG：卷组，由一个或多个物理卷组成 LV：逻辑卷，从卷组中切割出的空间，用于创建文件系统，供用户使用 命令： 功能/命令 物理卷（PV） 卷组（VG） 逻辑卷（LV） 扫描 pvscan vgscan lvscan 建立 pvcreate vgcreate lvcreate 显示 pvdisplay vgdisplay lvdisplay 删除 pvremove vgremove lvremove 扩大 无 vgextend lvextend 缩小 无 vgreduce lvreduce LV使用过程 将硬盘设置为物理卷 -&gt; 创建卷组 -&gt; 将物理卷加入卷组 -&gt; 切割指定大小的逻辑卷 -&gt; 格式化逻辑卷 -&gt; 创建挂载目录 -&gt; 挂载逻辑卷 -&gt; 添加开机挂载项 例1：系统中添加两个硬盘，创建物理卷及卷组，划分150MB的逻辑卷并格式化挂载使用 创建物理卷： pvcreate /dev/sdb /dev/sdc 创建卷组并将物理卷加入：vgcreate /dev/VG1 /dev/sdb /dev/sdc 显示卷组信息：vgdisplay /dev/VG1 切割150MB的逻辑卷：lvcreate -n lv1 -L 150M /dev/VG1 注意：lv1逻辑卷默认路径为卷组目录，使用相对路径即可 查看逻辑卷信息：lvdisplay /dev/VG1/lv1 格式化逻辑卷：mkfs.xfs /dev/VG1/lv1 创建挂载目录：mkdir -p /lvm1 挂载：mount /dev/VG1/lv1 /lvm1 开机挂载：添加新行 /dev/VG1/lv1 /lvm1 xfs defaults 0 0 例2：基于上例，划分出200MB的逻辑卷使用 划分200M的lv：lvcreate -n lv2 -l 50 /dev/VG1 注意：-n为新建参数，-l为PE（4MB）的个数，-L为实际容量 格式化：mkfs.xfs /dev/VG1/lv2 建立挂载点目录：mkdir -p /lvm2 挂载：mount /dev/VG1/lv2 /lvm2 开机挂载：/dev/VG1/lv2 /lvm2 xfs defaults 0 0 逻辑卷的扩容 过程： 卸载设备 -&gt; 扩容到指定容量 -&gt; 重新格式化 -&gt; 重新挂载 例：对lv1逻辑卷扩容到300M 查看lv1信息：lvdisplay /dev/VG1/lv1 卸载设备：umount /dev/VG1/lv1 扩容：lvextend -L 300M /dev/VG1/lv1 格式化：mkfs.xfs -f /dev/VG1/lv1 重新挂载：mount /dev/VG1/lv1 /lvm1 查看信息：lvdisplay /dev/VG1/lv1 注意： 扩容前必须先卸载设备 扩容时注意容量单位换算（-L与-l的区别） 扩容后未格式化，则容量不变 逻辑卷的缩容 原则：相对扩容，逻辑卷缩容有风险，需提前备份，缩容前需对逻辑卷进行文件系统完整性检查，以保证数据安全 过程： 卸载设备 -&gt; 文件完整性检查 -&gt; 缩容 -&gt; 重新格式化 -&gt; 重新挂载 例：同上例，对lv2缩容到100MB 卸载设备：umount /dev/VG1/lv2 文件完整性检查：xfs_repair -n /dev/VG1/lv2 缩容：lvreduce -l 25 /dev/VG1/lv2（需要输入y确认，-l 25为物理块个数） 查看信息：lvdisplay /dev/VG1/lv2 重新强制格式化：mkfs.xfs -f /dev/VG1/lv2 挂载：mount /dev/VG1/lv2 /lvm2 查看信息：lsblk 删除逻辑卷 删除顺序：逻辑卷 -&gt; 卷组 -&gt; 物理卷（顺序不能错乱） 注意： 删除前必须备份数据 删除时需要输入“y”来确认 过程： 卸载设备 -&gt; 清除fstab开机挂载中的对应数据 -&gt; lvremove删除逻辑卷 -&gt; vgremove删除卷组 -&gt; pvremove删除物理卷 例：将上例所有的LVM数据删除 卸载：umount /dev/VG1/lv1 umount /dev/VG1/lv2 清除fstab对应信息：vim /etc/fstab 删除对应行 删除逻辑卷：lvremove /dev/VG1/lv1 lvremove /dev/VG1/lv2（输入y确认） 删除卷组：vgremove /dev/VG1 删除物理卷：pvremove /dev/sdb /dev/sdc（取消硬盘对物理卷技术的支持） 查看信息：lsblk 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/13bSfXXJ9/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/13bSfXXJ9/"},{"title":"Linux命令学习（四）","content":"一、 磁盘管理 1. 硬盘命名规则 名称： 硬盘：/dev/sd[a-p] 光盘：/dev/sr0 /dev/cdrom 打印机：/dev/lp[0-15] 硬盘命名分析 硬盘名称以sd开头 使用了a-p代表16块不同的硬盘 主分区编号为：1 2 3 4 逻辑分区：从5开始 例：/dev/sda5：表示第一块硬盘的第一个逻辑分区 例：/dev/sdb2：表示第二块硬盘的第二个主分区 2. 添加新硬盘 关闭系统 编辑虚拟机设置 点击“添加” 选择硬盘类型 选择“SCSI”类型 创建新虚拟磁盘 设置容量（默认：20GB），将虚拟磁盘存储为单个文件 命名（默认） 完成 3. 分区格式 MBR（主引导记录区）分区格式 MBR数据存储于第0磁道第1扇区 作用：存储硬盘一系列分区参数及一段引导程序 组成： 原则：MBR所在扇区共512B MBR信息：446B DPT：硬盘分区表，64B 最后两个字节为结束标志 构成图： MBR分区特点： 主分区（P）：不能超过4个 扩展分区（E）：只能有1个 P+E最多4个 逻辑分区（L）：由扩展分区进行划分 一般使用：PPP+E的结构 GPT（磁盘分区表）分区格式 产生原因 MBR分区模式最高支持磁盘容量为2.2TB MBR分区表中没有备份机制，若分区表被删除，则硬盘数据相当于丢失 MBR存储相关信息只有446B，无法存储较多内容 分区格式 GPT分区使用LBA（逻辑区块）记录磁盘信息 LBA区块大小为512B-4K，支持68个LBA区块，其中前34个LBA区块记录分区信息，后34个LBA区块记录备份信息 LBA区块作用 LBA0：与MBR分区功能相同，并存储MBR分区标志或GPT分区标志 LBA1：存储GPT表头信息 LBA2-LBA33：记录分区信息，一个LBA区块可记录4个分区信息，共32个分区128个分区信息（32×4=128） GPT分区特点 GPT不再区分主分区和扩展分区，所有分区都是主分区，理论上可以划分128个主分区 GPT分区理论上最大支持磁盘空间为8ZB（1ZB=230TB） 4. 常见磁盘命令 df命令： 作用：列出磁盘使用量 格式：df -参数 目录名/文件名 参数 -a：列出所有文件系统 -h：以容易理解的方式列出信息（KB、MB、GB） 常用：df -ah 信息解释： 文件系统：文件系统名称及所在分区位置 已使用空间容量：不要超过90%的警戒线 du 命令： 作用：查看文件或目录占用硬盘空间容量 格式：du -参数 目录名/文件名 常用参数：du -ah lsblk命令： 作用：列出所有磁盘信息 格式：lsblk -参数 设备文件名 信息解释 NAME：设备名称 MAJ:MIN：主要:次要设备代码 RM：是否可卸载 SIZE：容量 RO：是否只读 TYPE：设备类型（disk磁盘，part分区等） MOUNTPOINT：挂载点 blkid命令 作用：显示设备的UUID值和文件系统名称 格式：blkid 设备名 UUID：全局单一标识符，系统给所有设备分配的唯一编号，以便于区别设备 5. 硬盘分区 parted 命令 作用：显示硬盘分区表及类型 格式：parted 设备名 print 注意：msdos表示MBR格式的分区模式 例：parted /dev/sda print fdisk命令 格式：fdisk 设备名 注意 fdisk命令只针对MBR分区格式进行分区 fdisk命令之后只能识别硬盘名不能书写分区数字 例：使用fdisk命令对/dev/sdb硬盘分区，格式：P+P+P+E（L+L） 分区命令：n（新建）、p（浏览分区列表）、d（删除）、w（保存退出）、q（退出） 分区编号直接回车即可 起始扇区直接回车即可 last扇区：输入容量（+数字单位，如+5G） 若将剩余空间全部分配，则last扇区处直接回车 例：fdisk 命令分区，格式：P+P+E（L+L+L） gdisk命令 注意：gdisk命令针对GPT分区格式，若在已存在的MBR分区格式下添加GPT新分区，则所有数据会全部丢失，切记一块硬盘中，fdisk与gdisk不能混用 格式：gdisk 设备名 例：使用gdisk对/dev/sdc进行分区，格式P+P+P gdisk /dev/sdc 输入n新建分区 回车，设置为默认的分区编号 起始扇区回车 终止扇区输入+4G 编号默认直接回车 重复上述过程 输入p查看分区列表 输入w回车输入y回车保存并退出 注意：实际工作中，硬盘分区应保留一定未分区自由空间，以备将来分区空间不足扩容时或添加新分区时使用 例：使用gdisk分区，P+P+P+P gdisk /dev/sdc 输入n新建分区 回车设置为默认的分区编号 起始扇区回车 终止扇区输入+4G 重复上述过程 输入p查看分区列表 输入w回车输入y回车保存并退出 lsblk /dev/sdc 查看分区信息 6. 分区格式化 作用：在硬盘分区上建立文件系统 格式化：若硬盘空间为一张白纸，分区是对其进行裁剪，对白纸进行方格绘制称为格式化 EXT2文件系统 组成： 超级块：又称为“硬盘地图”，记录文件系统的整体信息，如：数据块的总量、使用量等 索引块：记录文件属性，一个文件占用一个inode块，记录文件数据块的编号，默认占用128B，还存储文件的权限信息、账户信息、工作组信息、时间信息、地址信息等 数据块：存储文件真实数据的存储块，容量可划分为1K 2K 4K，一个文件会占用多个数据块。 索引块与数据块的关系 索引块大小均为128B 每一个文件只占用一个索引块 文件系统创建的文件容量与索引块的容量有关 一个索引块记录一个数据块号需要4B 当数据块为1K时，最大文件为16GB，文件系统总量为2TB；数据块为2K时，最大文件为256GB，文件系统总量为8TB；数据块为4K时，最大文件为2TB，文件系统总量为16TB 日志文件系统 产生原因：文件在写入时由于不明原因出现中断此时会发生文件内容不一致的情况，此时ext2文件系统会进行全盘检查，耗时较长 解决方法：在文件系统中重新规划一个区域为日志区块，专门记录文件修改的信息，简化文件一致性检查，若文件出现问题只检查相应日志区块即可，只修复部分信息，减少耗时 常见日志文件系统：EXT3、EXT4，Centos7默认的文件系统为xfs Centos7支持的文件系统格式：输入mkfs点击两次tab可查看 xfs格式化命令 格式：mkfs.xfs -参数 设备名 参数： -f：强制格式化，对已存在的文件系统格式化时使用 -b：后跟上block容量，表示格式化时数据块的容量，范围是512B-64KB，但Linux限制为4KB 例：mkfs.xfs /dev/sdb1 7. 设备挂载与卸除 挂载点：（mount point）：本只是一个目录，该目录是进入磁盘分区的入口 挂载：建立目录与分区之间的联系，通过目录进入分区空间并使用 注意： 单一的文件系统不能被重复挂载到不同的挂载点 单一目录不能重复挂载多个文件系统 作为挂载点目录，应为空目录，若已存在文件，则原有数据会被隐藏 mount命令 格式：mount -参数 设备名 挂载目录名 -a：依照配置文件（/etc/fstab）的数据将未挂载的设备全部挂载，主要用于测试 例：新建4个目录挂载/dev/sdb的四个分区 mkdir -p /data/sdb1 /data/sdb2 /data/sdb5 /data/sdb6 mount /dev/sdb1 /data/sdb1 mount /dev/sdb2 /data/sdb2 mount /dev/sdb5 /data/sdb5 mount /dev/sdb6 /data/sdb6 lsblk /dev/sdb 挂载光盘：mount /dev/sr0 /media 卸载命令 格式：umount -参数 设备名/挂载点名 参数： -f：强制卸载 -l：立即卸载文件系统 -n：不更新/etc/mtab情况下卸载 例：umount /dev/sdb1 ​ umount /data/sdb2 注意：卸载时，当前目录不能是挂载点目录，必须退出后才可卸载，否则显示设备正忙 开机挂载命令 原因：由于手动挂载为临时挂载，系统重启后会自动卸载 开机挂载必须修改配置文件实现 /etc/fstab 开机挂载挂载原则 根目录分区必须先挂载 其他挂载点必须为已建立目录，应遵守系统目录架构原则（FHS） 所有挂载点在同一时间内只能挂载一次 所有分区在同一时间内，只能被挂载一次 fstab意义解释 共6列 第一列：设备名称或UUID值 第二列：挂载点目录 第三列：文件系统 第四列：文件系统参数，默认defaults 第五列：0（不备份），是否备份 第六列：0，是否开机进行扇区检查，xfs文件系统不支持则为0 例：将/dev/sdb 4个分区进行开机挂载 vim /etc/fstab 点击i键编辑，添加新行 /dev/sdb1 /data/sdb1 xfs defaults 0 0 保存并退出 mount -a 进行测试 reboot 重启 lsblk /dev/sdb 例：添加新硬盘sde，对sde进行如下操作 硬盘进行gdisk分区：P+P+P（3G+5G+6G） 对硬盘进行格式化：sde1，sde2为xfs，sde3为ext4 新建挂载目录：/mnt/s1 /mnt/s2 /mnt/s3 进行开机挂载 查看信息 8. 大文件挂载为分区 当硬盘无自由空间，且需要增加新分区，可以将已存在分区的剩余空间中划出一个大容量文件，格式化后挂载为一个分区使用 例：在/dev/sdb1中制作1G大文件挂载为新分区 制作大文件：dd if=/dev/zero of=/data/sdb1/loopdev count=1 bs=1G 格式化文件：mkfs.xfs -f /data/sdb1/loopdev 建立挂载点目录：mkdir /data/sdb1/loopdir 挂载大文件：mount -o loop /data/sdb1/loopdev /data/sdb1/loopdir 查看挂载情况：df -h 注意：挂载大文件时，参数需增加 -o loop参数 9. 增加swap交换分区空间 swap：类似于windows的虚拟内存，将一块硬盘中的剩余空间虚拟成 “内存” 使用，从而解决内存不足的问题 swap空间大小：4-8GB 查看swap空间容量： free -h 创建新分区制作swap空间 使用 gdisk 对 /dev/sdc 创建4个分区，容量（2,3,4,2） 使用 gdisk 对 /dev/sdc 添加一个新分区sdc5（2G） 格式化分区：mkswap /dev/sdc5 启用生效：swapon /dev/sdc5 查看：free -h 开机挂载swap：必须设置开机挂载，否则重启失效 vim /etc/fstab 添加新行：/dev/sdc5 swap swap defaults 0 0 保存并退出 mount -a 进行测试 reboot 重启 free -h 查看是否生效 创建大文件制作swap空间 原则：由于硬盘没有自由空间，无法添加新分区，则在某分区剩余空间制作大文件并创建成swap空间 例：在/dev/sdb5分区中制作2G大文件并生成swap空间 制作大文件：dd if=/dev/zero of=/dev/sdb5/swapfile count=2 bs=1G cd /data/sdb5 ls 格式化：mkswap /data/sdb5/swapfile 启用生效：swapon /data/sdb5/swapfile 查看：free -h 配置开机挂载：vim /etc/fstab 添加新行：/data/sdb5/swapfile swap swap defaults 0 0 保存退出 测试：mount -a 重启：reboot 查看：free -h 注意：fstab配置文件中，若添加的对象是大文件，则第一列不能使用UUID值 10. 软硬链接方式 硬盘文件存储原理： 硬盘数据分为两类：用户数据（UserData）与 元数据（MetaData） 用户数据：文件的数据块，存储文件数据的实际空间 元数据：存储文件附加属性，如文件大小，创建时间、所有者等信息 Linux中元数据的inode号（索引节点号）是文件的唯一标识，而非文件名，系统或程序通过inode号寻找正确的文件数据块 图： 硬链接 原理：由于Linux系统允许多个文件指向同一个inode即允许一个文件有多个有效路径名 硬链接本质为指向原始文件的inode指针 查看硬链接方法： ll 文件名 stat 文件名 建立硬链接方法： 格式：ln -参数 源文件 目标链接名 例：touch t1.txt ​ echo &quot;Hello World&quot; &gt; t1.txt ​ stat t1.txt ​ ln t1.txt t2.txt ​ ln t1.txt t3.txt ​ stat t1.txt ​ cat t2.txt ​ rm -rf t1.txt ​ cat t3.txt 注意： 新建文件的硬连接默认为1，即文件名指向数据块 创建多个硬链接后，删除源文件，数据依然可以访问 只有源文件删除且硬链接数为0时，数据相当于删除，不可访问 硬链接不能给目录建立 目录默认带有2个硬链接，实际为目录中的点（.）和点点(..) 硬链接不可以在不同的文件系统中建立，由于inode是当前文件在本文件系统的索引号，只针对本分区空间，所以硬链接不能跨越文件系统 软链接 原理：是一种符号链接，类似于windows的快捷方式，仅包含所链接的路径名 格式：ln -s 源文件 目标链接名称 例： 例：创建软链接，减少访问文件时间 软硬链接区别 硬链接中源文件与链接文件共用一个inode号，是同一个文件，软链接中源文件与链接文件拥有不同的inode号，是两个不同的文件 软链接可以查看链接路径，硬链接不可查看 硬链接可以增加链接数，软链接不变 文件大小不同，硬链接文件大小与源文件相同 软链接可以跨越文件系统或分区 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/iKIiXSumE/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/iKIiXSumE/"},{"title":"Linux命令学习（三）","content":"一、文件系统命令 1. FHS 1.1 FHS：文件系统层次化标准 1.2 拓扑：倒置的树形结构，一切都从根目录（/）开始 1.3 常见一级目录存储文件功能 /boot：存储开机所需文件，如：内核、镜像、菜单的配置文件 /dev：以文件的形式存储设备及接口，如：硬盘、光驱等 /bin：存储命令程序及配置文件 /lib：开机时用到的函数及数据 /root：root账户的家目录，相当于~ /var：存储系统产生的日志文件 /media：用于挂载设备（光驱）的目录 /user：存储应用程序的安装目录 /etc：配置文件存储目录 1.4 注意：目录名称区分大小写，FHS意义在于按照功能进行分类存储文件 2. 文件操作命令 2.1 新建目录命令 格式：mkdir -参数 目录名称 参数： -p：创建递归目录（包含下级目录的多级目录） -m：对新建的目录设置存取权限 例： 2.2 新建文件命令 格式：touch -参数 文件名 例1：touch n{1,2,3,4,5,6,7,8}.txt 例2： 例3： touch test.c vim test.c #include&lt;stdio.h&gt; int main(void){ printf(&quot;Hello World&quot;); return 0; } gcc test.c ls -a ./a.out 2.3 删除命令 格式：rm -参数 文件名 参数： -r：删除目录 -f：强制删除，不确认 2.4 pwd命令：显示绝对路径 2.5 cp拷贝命令 格式：cp -参数 源文件 目标文件 参数： -r：拷贝目录 -p：保留原始文件属性 -d：若为链接文件，则保留链接文件属性 注意： 若目标为目录，则拷贝到目录中 若目标为文件且不存在，则正常拷贝 若目标为文件且名称不同，则改名后拷贝 2.6 mv移动文件命令 格式：mv -参数 源文件 目标文件 注意： 若目标为目录，则剪切移动到目录中 若目标为文件，则修改名称（无同名目录） 2.7 大容量文件生成命令 格式：dd if=数据来源 of=新文件名 count=数据块个数 bs=单个块容量 功能：按照指定容量制作大文件，数据来源一般为算法设备或光驱 if=/dev/zero：zero为可输出指定容量大小的数据0 if=/dev/sr0：光驱（cdrom） 例： 制作400MB大文件：dd if=/dev/zero of=xin.txt count=2 bs=200MB 制作光盘镜像：dd if=/dev/sr0 of=centos7_x86_64.iso 2.8 file命令 作用：查看文件类型 格式：file 文件名 文件类型 empty：空文件 ASCII File：ASCII文件 character special：字符设备文件 block special：块设备文件 directory：目录 link：链接文件 2.9 cat命令 作用：输出文本内容 格式：cat 文件名 参数： -n：显示行号 -b：排除空行，非空行显示行号，从1开始显示 将文件显示内容输出到另一文件 touch temp.txt cat /etc/passwd &gt; temp.txt cat temp.txt 文档合并： 2.10 tac命令 作用：倒序查看文件内容 格式：tac 文件名 2.11 more命令 作用：翻页查看文件内容，适用于内容较多的文件 格式：more 文件名 参数 -s：将输出文件中的空行减少为1个空行 -p：先清屏后显示 -数字：如-10，表示1屏显示10行，包括空行 例： more -sp /etc/passwd more -5 /etc/passwd 注意： 点击空格进行翻页浏览 点击回车进行换行浏览 点击q键退出浏览 2.12 less命令 作用：同more，使用键盘上下键浏览 2.13 head命令 作用：显示文本文件的前n行 格式：head -数字 文件名 2.14 wc命令 作用：统计文本文件内容的个数 格式：wc -参数 文件名 参数 -l：显示行数 -w：显示单词数 -c：显示字节数 不加参数全显示 例：显示文件前十行后统计字节数 2.15 stat命令 作用：显示文件的存储信息 格式：stat 文件名 3. 文件查找命令 作用：按照条件查找文件 格式：find 查找路径 查找条件 操作 查找条件： 文件名称 -name：按照文件名检索，文件名最好加上双引号 通配符： *：表示任意内容匹配任意次数 ？：表示任意内容匹配一次 以扩展名查询文件 find /etc -name &quot;*.cfg&quot; 文件属性 -user：按照所有者进行匹配（root账户或普通账户） find ~ -user &quot;root&quot; find ~ -name &quot;a*&quot; -user &quot;root&quot;（检索以a开头且归属于root账户的文件） find / -name &quot;*e&quot; -o -user &quot;ZHIYUAN&quot;（检索以e结尾或归属于ZHIYUAN账户的文件） 设备 -type b/d/c/p/l/f：按照文件类型检索（块设备、目录、字符设备、管道设备、链接文件、文本文件） find /dev -type b （检索块设备文件） find ~ -type d （检索目录） 权限 -perm：按照权限检索 find /dev -perm 777 时间 -mtime：按照修改文件时间检索 -atime：按照访问文件时间检索 -ctime：按照修改权限时间检索 注意：-n表示n天以内，+n表示n天以前 例：find / -mtime -10 -empty（检索10以内未修改的空文件） 容量 -size：按照容量来检索（k、M、G） find /dev -type c -size -10k（检索/dev目录下的容量小于10k的字符设备文件） find / -size +100M（检索根目录下容量大于100M的文件） 4. 文件内容过滤命令 作用：在一个文件中按照查找字段进行过滤内容 格式：grep -参数 查找条件 文件名 参数： -n：显示检索内容的行号 -i：忽略字母大小写 -v：根据检索关键字进行反向显示 -c：统计符合条件的行数 -A数字：如-A3，显示符合条件的当前行和以下3行，向下遍历（4行） -B数字：如-B3，显示符合条件的当前行和以上3行，向上遍历（4行） -C数字：如-C2，显示符合条件的当前行以及上下2行（5行） 例： 检索包含“root” 的行信息：grep -n &quot;root&quot; /etc/passwd 检索passwd文件中不能登录的信息：grep -n “/sbin/nologin” /etc/passwd 检索passwd文件中能登录的信息：grep -n -v &quot;/sbin/nologin&quot; /etc/passwd 检索包含“ssh”字段的行以及上5行：grep -nB5 &quot;ssh&quot; /etc/passwd 检索包含“ftp”的行及上下两行：grep -nC2 &quot;ftp&quot; /etc/passwd 检索不能登录账户的行数：grep -c &quot;/sbin/nologin&quot; /etc/passwd 检索包含大小写字母d的信息：grep -ni &quot;d&quot; /etc/passwd 正则表达式 概念：为了处理大量文本或字符串而定义的一套规则方法 特点： 通过定义特殊符号来描述模糊查找关键字，可以快速过滤、替换、输出字符串 检索时是按行为单位进行处理 使用[ ]字符集表示 [ab]：检索包含或者 b的行信息，[]中只能表示一个内容 例：检索包含字母d或者D的信息：grep 例：检索包含字母o的信息：grep -n [o] /etc/passwd [^]：根据关键字检索后反向显示（反向着色） 例：检索不包含字母o的信息：grep -n [ ^o ] /etc/passwd 例：检索不以r开头且包含oot的信息：grep -n [ ^r ]oot /etc/passwd [-]：[0-9] 表示数字，[a-z] [A-Z]表示小写或大写字母，[a-zA-Z]表示字母 例：检索包含7-9的信息：grep -n [7-9] /etc/passwd 例：检索包含大写字母：grep -n [A-Z] /etc/passwd 行首及行尾 ^：表示行首 例：检索以“root”开头的行信息：grep -n &quot;^root&quot; /etc/passwd 例：检索以#开头的行信息：grep -n &quot;^#&quot; /etc/sos.conf 例：检索以[开头的信息：grep -n &quot;^\\[&quot; /etc/sos.conf（注意：\\转义） $：表示行尾 例：检索以数字结尾的信息：grep -n [0-9]$ /etc/sos.conf 例：检索空白行：grep -n ^$ /etc/sos.conf 例：检索非空行：grep -nv ^$ /etc/sos.conf 正则通配符 点（.）：表示一个任意字符 例：检索以r开头，t结尾的中间2个字符的信息：grep -n &quot;r..t&quot; /etc/passwd 星号（*）：重复前面字符 0次或多次 例：检索至少包含2个字母o的信息：grep -n &quot;ooo*&quot; /etc/passwd 转义字符 作用：由于某些字符有特殊作用，若使用该字符原来的功能，则需要使用\\将其特殊功能去掉，称为转义 例：检索以点结尾的字符：grep -n &quot;\\.$&quot; tt.txt 区间字符 格式：\\{n1,n2\\} 分析：由于{}有特殊作用，需要使用\\进行转义，n1==n2时，可以简写为\\{n1\\}，表示执行n1次 例：检索包含两个o的信息：grep -n &quot;o\\{2\\}&quot; /etc/passwd 例：检索包含两个到五个o的信息：grep -n &quot;o\\{2,5\\}&quot; /etc/passwd 5. 文档内容替换命令 作用：进行文本内容的批量替换 格式：tr 原字符 结果字符 例：将passwd文件中的小写字母改为大写：cat /etc/passwd | tr [a-z] [A-Z] 例：grep -n &quot;root&quot; ~/passwd | tr [a-z] [A-Z] 6. 打包压缩命令 作用：将文件或目录进行打包、压缩、解压缩 分类： *.gz：由gzip程序进行压缩、解压缩 *.bz2：由bz2程序压缩、解压缩 *.tar：只是打包文件 *.tar.gz：使用tar打包后进行gzip压缩 注意：*.tar.bz2 和 *.tar.xz 也是打包压缩文件 tar命令 格式：tar -参数 文件名 目标及存储路径 参数： -c：创建压缩文件 -x：解压缩文件 -v：显示时间进度 -f：目标文件名 -p：保留原始文件的属性及权限 -t：查看压缩包的内容 -P：使用绝对路径进行压缩 常用参数： -cvf：压缩 -xvf：解压缩 例：tar -cvf rootfile.tar.gz /root ​ tar -xvf rootfile.tar.gz 注意：参数c和参数x不能同时使用 7. 管道符与重定向符 重定向 分类： 输入重定向：默认来源于键盘 输出重定向：默认到屏幕 符号： 命令 &lt; 文件：将文件作为命令的标准输入 命令 &gt; 文件：将标准输出重定向为文件（覆盖清空原有内容） 注意 &gt;&gt; 表示追加，会保留原有内容 管道符 符号：| 格式：命令1|命令2|命令3....... 作用：将前一个命令的输出结果作为下一个命令的标准输入 8. 环境变量 作用：用来定义系统运行环境的相关参数 一般使用大写英文字母 PATH环境变量 作用：存储Linux系统命令解释器bash及程序的路径 查看：ehco $PATH 常见环境变量 HOME：存储家目录的绝对路径 HISTSIZE：记录history命令的历史命令条数 LANG：记录语系 MAIL：邮箱文件 执行命令过程 第一步：判断是否以绝对路径或者相对路径来书写命令，若为绝对路径则立即执行 第二步：判断命令名是否为别名 第三步：使用bash解释器判断命令是外部命令还是内部命令 第四步：通过PATH查询路径执行 查看环境变量：$ 格式：$ 变量名 全局变量： 作用：所有账户都生效的变量，默认定义的为局部变量 格式：export 变量名 例： 在root账户下： export NIC=/etc/sysconfig/network-scripts/ifcfg-ens33 cat $NIC 在普通账户下 su ZHIYUAN cat $NIC 二、 用户账户及权限 1. root账户 root账户为默认的系统管理员账户，拥有完整权限 使用原则：生产活动中尽量不使用root账户，应使用普通账户临时分配权限来使用 2. UID 账户ID是管理账户的一种手段，相当于账户的“身份证编号”，具有唯一性 编号原则 系统使用无符号整数进行编号，范围：[0,4294967294] UID分类 系统管理员账户：[0,999]，root账户UID为0 普通账户：从1000开始编号，连续编号，即使管理员编号未使用，普通账户也不可占用 查看：/etc/passwd id命令 作用：显示账户的UID值 格式：id 账户名 注意：UID不能相同 3. 新建账户 格式： useradd -参数 账户名 参数： -d：指定账户家目录，默认普通账户家目录为：/home/账户名 -e：设置账户到期失效时间，时间格式：YYYY-MM-DD（如：2020-10-08） -u：指定账户的UID值 -g：指定一个初始用户的工作组（工作组必须已存在） -G：指定一个或多个扩展组 -N：不创建与账户同名的基本工作组 例：useradd test1 4. 设置账户密码 格式：passwd -参数 账户名 参数： -l：锁定账户，禁止登录 -u：解除锁定，允许登录 -d：允许用户可以用空格密码登录 -e：强制用户在在下次登录时修改密码 注意： 密码输入时不回显 密码应超过8字符 密码应使用数字、字母、符号组成 密码应定期更换 例1：新建账户、设置密码、注销账户、重新登录 useradd test2 passwd test2 （输入两次密码） logout 登录test2账户 例2：新建账户test3，UID=5555，工作组为test1，家目录为/home/tmp useradd test3 -u 5555 -g test1 -d /home/tmp（/homt/tmp需要新建） passwd test3（输入两次密码） logout 登录test3账户 pwd id test3 例3：设置账户为不可登录状态 vim /etc/passwd 定位倒数几行的普通账户，点击i键，将本行尾部的/bin/bash修改为/sbin/nologin 点击ESC键，冒号，wq保存并退出 logout 尝试登录被修改账户 root账户密码的破解 reboot重启系统 在内核选择界面点击键盘上下键，再点击e键 进入内核登录编辑界面，定位Linux16所在行，找到ro修改为rw init=/sysroot/bin/bash 按下Ctrl+X 进入紧急救援模式后输入chroot /sysroot passwd修改密码（输入两次密码，方块为汉字不能显示） 设置selinux 使其密码修改生效：touch /.autorelabel 按下Ctrl+D reboot重启 5. 删除账户 格式：userdel -参数 账户名 参数： -f：强制删除 -r：同时删除账户及家目录 6. 工作组（group） 意义：将相同属性及权限的账户作为一组，组内账户属性等信息相同 Linux系统为了方便管理数据，引入了层级管理机制：公司（系统）-&gt;部门（工作组）-&gt;员工（账户）-&gt; 文件（权限） 分类 基本工作组：创建账户时一同创建同名的工作组 扩展组：可以将账户加入到其他工作组中 查看：more /etc/group GID：工作组编号，规则同UID 创建工作组 格式：groupadd -参数 工作组名 参数：-g ，自定义工作组GID，默认从1000开始 删除工作组 格式：groupdel 工作组名 注意：若工作组中已存在账户，必须先移除再删除工作组，否则无法删除工作组 7. 文件权限 文件归属者 文件拥有者（账户） 所属组（工作组） 其它账户 权限的意义 限制用户操作，规范命令使用，将破坏性限制在一定范围 类别 普通文件（-） 可读（r）：可以读取到文件中的内容 可写（w）：可以编辑修改删除文件内容 可执行（x）：可以运行一个脚本 目录（d） 可读（r）：可以读取目录中的文件列表 可写（w）：可以读取目录内容，修改目录名称，增删目录中的文件 可执行（x）：可以进入该目录（cd命令） 查看权限方法： ls -l（ll同）：查看目录内的文件权限信息 ls -ld：查看当前目录的权限信息 显示内容分析： 第一列描述文件类别，- 表示普通文件，d为目录 rw-r--r--：三个一组，分别对应账户、工作组、其他账户对该文件拥有的权限，- 表示无权限 1：链接数 root root：该文件对应的账户及所属工作组 权限数字表示法 原则： r：4 w：2 w：1 小结： 权限项 读 写 执行 读 写 执行 读 写 执行 字符表示法 r w x r w x r w x 数字表示法 4 2 1 4 2 1 4 2 1 权限分配 文件所有者 文件所属组 其他账户 组合 rwx：4+2+1-&gt;7（完整权限） rw-：4+2-&gt;6（可读可写不可执行） r-x：4+1-&gt;5（可读可执行不可写） r--：4（可读不可写不可执行） -wx：2+1-&gt;3（不可读可写可执行） -w-：2（可写） --x：1（可执行） ---：0（无权限） 例：764、642、153、731的字符描述 rwxrw-r-- rw-r---w- --xr-x-wx rwx-wx--x 例：rwxrw-r-- 、rw--w--wx 、rw-r--r--数字描述 rwxrw-r--：764 rw--w--wx：623 rw-r--r--：644 修改文件权限命令chmod 格式：chmod -参数 权限 文件或目录名 例：touch tmp.cfg（新建文件默认权限644） ​ chmod 777 tmp.cfg chown命令 作用：设置文件所有者及工作组 格式：chown -参数 账户名:工作组 文件或目录名 例： chown ZHIYUAN:ZHIYUAN tmp.cfg 文件隐藏权限 原则：文件一般包含默认权限和隐藏权限，隐藏权限默认状态下无法查看，若权限足够但无法删除，此时应考虑该文件是否有隐藏权限 lsattr命令 作用：查看文件隐藏权限 格式：lsattr 文件或目录名 chattr命令 作用：设置文件隐藏权限 格式：chattr 参数 文件或目录名 参数 i：表示文件无法修改（目录中不能新建和删除） a：仅允许追加内容，不可以覆盖、删除 s：从硬盘中彻底删除，不可恢复，会使用0填充文件所在存储空间 S：文件变更后同步到磁盘 注意：参数前若有加号（+）或减号（-），则表示增加或撤销功能 例：设置不可删除权限 8. 账户切换命令 格式：su -参数 账户名 常用：su - 账户名 注意： 减号（-）表示完全切换，重新建立系统环境变量，不保留原有环境参数 root账户切换到普通账户，不需要输入密码，反之需要重新验证密码 9. sudo命令 作用： 给普通账户提供额外的权限完成原本root账户才能实现功能，将权限控制在某个命令或文件上，降低安全风险 sudo配置文件分析 原则：必须在root账户下配置后才可使用 格式：visudo 方法： visudo 输入后定位第99行即：root ALL=(ALL) ALL 该参数意义为：账户名 允许使用的主机=（以谁的身份） 执行命令列表 点击i键输入：普通账户名 ALL=(ALL) ALL 输入冒号，wq保存并退出 提权方法： 格式： sudo -参数 命令名称 例：新建账户访问 /etc/shadow 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/xc3aA90Gk/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/xc3aA90Gk/"},{"title":"Linux命令学习（二）","content":"一、 常见的Linux命令 1. Shell的简介 1.1 shell（壳）是一种强大的命令行工具，位于用户与系统内核之间，起到“翻译官”的作用，即识别解释用户输入的命令，并提供操作界面 1.2 结构 1.3 特点 1.3.1 系统默认提供了六个shell工具（五个命令行，一个GUI界面） 1.3.2 使用键盘“上下键↑↓”，可以查看输入的历史命令 1.3.3 GUI界面下的命令行可使用鼠标选定指定命令后按下鼠标中键复制进行粘贴 1.3.4 使用Tab键进行命令单词的补全 1.3.5 Linux系统中一切数据皆是文件 2. 命令提示符 [root@localhost ~]# root：当前已经登录正在使用的账户 @：分隔符 localhost：主机名 ：表示当前工作目录， 表示 帐户家目录 #：如果是root用户，结尾标志是#，如果是普通账户，结尾标示是$ 3. 命令格式 3.1 格式： 3.1.1 短格式： 命令名称 -参数 目标 3.1.2 长格式： 命令名称 --参数全称 目标 3.2 命令目标的路径 3.2.1 相对路径：以当前工作目录起始，进行遍历 3.2.2 绝对路径：以根目录（/）起始，进行遍历 4. 目录切换命令 4.1 格式：cd 目录名 4.2 例： cd ~ ：切换到管理员的家目录 cd / ：返回根目录 cd .. ：返回上一级目录 5. 目录浏览命令 5.1 格式：ls -参数 目录名 5.2 作用：查看目录内的文件数据 5.3 命令行终端模式下的目录颜色分类： 蓝色：目录文件（文件夹） 浅蓝色：链接文件（快捷方式） 黄色：设备文件 白色：普通文件 红色：压缩文件 绿色：可执行文件 5.4 常用命令 ls -a：显示所有文件（包括隐藏文件） ls -l：查看文件目录内文件的详细信息 （文件权限+连接数+所属用户+所属用户组+大小+创建时间+文件名） ls -ld：查看当前目录的详细信息 ls | more：分页查看内容较多目录的信息（空格翻页，回车换行） ll ：相当于 ls -l 6. date命令 6.1 作用：查看设置日期时间 6.2 格式： date -参数 +日期时间格式 6.3 参数： -s：设置日期时间 -d：显示描述的日期时间 6.4 日期时间格式 %Y：完整的年份 %m：月份（01-12） %d：日（01-31） %j：一年中的第几天（001-366） %H：小时（00-23） %M：分钟（00-59） %S：秒（00-59） %X：相当于 %H:%M:%S %A：显示星期几（Sunday-Saturday） %Z：显示时区 6.5 例： date +%Y%m%d date +%Y-%m-%d date +%H:%M:%S date &quot;+%Y-%m-%d %H:%M:%S&quot; date +%Y-%m-%d%t%H:%M:%S date +%Y-%m-%d%t%H:%M:%S%A%t%Z%t%j date &quot;+%Y-%m-%d %H:%M:%S %A %Z %j&quot; 参数中有空格必须加双引号，或者将空格替换为%t 6.6 注意：%j参数表示一年中第几天，一般用于文件备份时加入此参数，数字越小文件越旧，数字越大，文件越新 6.7 设置日期时间：date -s &quot;2020-10-6 10:58:30&quot; 6.8 显示指定日期时间：date -d &quot;+2 day&quot; +%d 08 7. 显示日历 格式：cal 月份 年份 cal 05 2020 8. 系统状态检测命令PS 8.1 作用：查看进程状态，一般用于获取进程ID 8.2 格式：ps -参数 8.3 常用命令 ps ps -u：描述所有用户进程的详细信息 有用的信息： %CPU：CPU占用率 %MEM：内存使用率 VSZ：虚拟内存使用率 stat：进程状态 R：运行状态 S：休眠状态 D：中断状态 Z：僵死状态 T：停滞状态 9. Top命令 作用：动态显示进程及进程状态，是一种强化版的任务管理器 第一行：系统时间、运行时间、登录终端数、系统负载（1分钟平均值、5分钟平均值、15分钟平均值） 第二行：进程数、运行数、睡眠数、停止数、僵死数 第三行：CPU用户占用百分比、系统内核占用资源百分比、改变过优先级进程占用资源百分比、空闲资源百分比 第四行：内存总量（单位：KB）、空闲量、使用量、作为内核缓存的内存量 第五行：swap总量（单位：KB）、空闲量、使用量、提前加载的使用量 注意：使用q键退出查看 10. 进程终止命令kill kill 进程名/PID进程编号 killall 进程名/PID进程编号 11. 查看网卡信息命令 格式：ifconfig mtu：网络传输最大数据包容量 inet：IP地址 netmask：子网掩码 broadcast：广播地址 RX：发送数据包容量，单位：KB TX：接收数据包容量，单位：KB 12. 查看本机信息命令 格式：uname -a 13. 查看内存信息： free free -h 以具体单位查看内存信息 free -g 以GB单位查看内存信息 free -m 以MB单位查看内存信息 free -k 以KB单位查看内存信息 free -b 以Byte单位查看内存信息 free -s 时间（单位：秒） 以一定时间间隔连续查看内存使用信息 14. who命令与last命令 作用：显示当前登录用户及终端信息 终端标志 ：0 表示GUI可视化终端 pts/0 表示远程登录方式 tty2：命令行终端 15. uptime 作用：显示系统平均负载 16. history 作用：查看历史命令记录 修改命令历史记录条数 文件路径：/etc/profile 修改变量：HISTSIZE=1000之后的数字即可 过程： vim /etc/profile 点击i键后定位在HISTSIZE=1000所在行 修改数字后点击ESC键 点击shift+：输入wq保存并退出 reboot重启 注意：HISTSIZE=0，表示不记录历史命令 常用命令： -c：清空当前历史命令 17. echo命令 作用：将字符串显示到屏幕上或输出变量的值 例：echo “HelloWorld” echo $SHELL 常用命令 -e：激活转义字符 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/hNHvS29UU/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/hNHvS29UU/"},{"title":"Linux命令学习（一）","content":"一、Linux操作系统初识 Linux操作系统 = 内核（kernel）+操作界面+第三方应用程序 版本介绍： 2.1 RedHat：红帽企业软件系统，企业常用 2.2 Centos：社区企业操作系统，学习/企业首选，与RedHat功能完全一致 2.3 Ubuntu：乌班图，面向消费级平台，日常使用首选 二、开发环境的搭建 1. 虚拟机VMware的安装 增强型键盘驱动（必须打钩）：识别不同硬盘设备 输入序列号 2. Centos7系统版本选择 阿里巴巴开源镜像站：https://developer.aliyun.com/mirror 网易开源镜像站：https://mirrors.163.com Centos DVD 版本：使用原软件DVD制作的镜像，功能全面（常用基本都有），推荐使用（4.5G左右） Centos Everthing 版本：完整版本（10G左右），不推荐使用 Centos NetInstall 版本：网络安装及救援使用（500M左右） Centos Minimal 版本：最小功能安装包（1G左右） Centos LiveGNOME 版本：GNOME桌面版本 Centos LiveKDE 版本：KDE桌面版本 注：x86_64及amd64都表示64位系统 I386：32位系统 desktop：桌面版 server：服务器 3. Centos7系统的安装 3.1 创建新的虚拟机：典型 注： 3.2 需要自定义配置，否则是最小化安装，选择稍后安装系统 3.3 选择客户机操作系统：Linux-&gt;Centos7 64位 3.4 命名虚拟机 按照功能命名 存储在非C盘的目录下 容量20GB，动态分配，此容量为最大值 单个文件：存取效率高，但不方便数据迁移 多个文件：存取效率低，但是方便数据迁移 3.5 自定义硬件 内存：推荐2GB CPU：1颗，核心数以实际物理机为标准 新CD/DVD：设置系统启动文件，选择“使用ISO镜像文件”选项，后点击“浏览”选择之前已经下载好的iso镜像文件 网络适配器：选择NAT模式 桥接模式：适用于网络固定不变的环境中 NAT模式：适用于网络环境经常发生变化的环境中 仅主机模式：表示Linux系统只能与本机物理机通信无法连接互联网 3.6 安装系统 软件选择：带有GUI的服务器 安装位置（硬盘分区） 概念： 挂载点：（windows的分区） 手动分区 系统启动引导分区：400MB /boot 交换分区（虚拟内存分区)：4GB swap 根分区，剩余空间（容量不填) / KDUMP：内核崩溃转储机制，生产环境中开启 设置root用户密码，创建一个新用户 3.7 Centos系统功能的基本配置 登录系统 终端切换（Ctrl+Alt+F1/F2/F3/F4/F5/F6） 共提供6个终端界面，5个命令行界面，1个GUI界面，使用Ctrl+Alt+F1~F6进行切换 网卡配置 cd /etc/sysconfig/network-scripts/ vim ifcfg-ens33 此时处于浏览状态，按i进入编辑状态 ONBOOT=no 改为 ONBOOT=yes ESC退出编辑状态，进入浏览状态 输入冒号进入命令状态，输入wq，保存并退出 重启网卡服务： service network restart 测试网络：ping www.baidu.com，Ctrl+C终止 yum源替换 [base] name=CentOS-$releasever - Base mirrorlist=http://mirrorlist.centos.org/?release=$releasever&amp;arch=$basearch&amp;repo=os&amp;infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 下载地址：https://mirrors.aliyun.com/repo/Centos-7.repo 原因：由于系统自带yum源位于国外，下载软件过慢，则需要更改yum配置文件为国内常用的开源镜像网站（阿里，华为，网易） 过程 ① 删除原有yum配置文件目录 cd /etc rm -rf yum.repos.d ② 新建该目录 mkdir yum.repos.d ③ 下载阿里开源镜像站的centosyum源配置文件 cd yum.repos.d wget https://mirrors.aliyun.com/repo/Centos-7.repo ls ④ 建立新的yum缓存并生效：yum makecache 更新软件 内核：yum update kernel 软件：yum update 软件名 注：yum update 表示更新所有已安装软件 reboot重启系统 制作快照：虚拟机-&gt;快照-&gt;拍摄快照-&gt;命名-&gt;拍摄快照 Putty远程登录软件 作用：使用putty远程登录到服务器进行操作控制 方法： ① 在Linux系统中查看本机IP地址：ip addr ② Putty软件中点击：Hosts Name（or IP address）输入之前查看的IP地址 ③ Save Sessions：命名并点击右侧save按钮保存 ④ 点击open按钮打开 ⑤ 点击yes ⑥ login as：输入用户名 ⑦ 输入密码 字号的更改： 右键单击标题栏-&gt;Change Settings-&gt;Window-&gt;Appearance-&gt;Change按钮-&gt;设置字体属性-&gt;Apply应用 常用快捷键： Ctrl+C：终止命令 Ctrl+L：清屏命令 Ctrl+D：结束当前终端 Ctrl+Z：终止进程 reboot：重启 shutdown now：关机 例：下载安装一个屏保软件 下载屏保软件： cd ~ wget http://archive.ubuntu.com/ubuntu/pool/universe/c/cmatrix/cmatrix_1.2a.orig.tar.gz 解压文件 tar xvf cmatrix_1.2a.orig.tar.gz 进入安装目录 cd cmatrix-1.2a 安装调用库及编译器 yum install -y ncurses-devel yum install -y gcc 编译安装软件： ./configure &amp;&amp; make &amp;&amp; make install 运行软件： cmatrix 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/BdIGGzKkE/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/BdIGGzKkE/"},{"title":"Spring+SpringMVC+MyBatis学习","content":"SSM 传统Servlet的不足 同一个类型的业务要进行多个Servlet的编写，单个Servlet功能单一 对象管理不方便，每次操作业务，都会产生很多对象 Java中的主流框架技术 Struts Hibernate Spring SpringMVC SpringBoot MyBatis 学习框架的思路 框架的规范 框架的底层实现 Struts2框架的介绍 是一个基于mvc设计思想的web应用框架，它本质就是一个Servlet，在mvc设计思想中，Struts2充当的是一个Controller的角色 Hibernate框架的介绍 开源的对象关系映射框架（ORM）框架 底层就是对JDBC的封装 Java企业级开发的演化 Servlet+JavaBean Servlet+JSP+JavaBean Struts2+Spring+Hibernate:SSH Spring+SpringMVC+MyBatis:SSM SpringBoot(Spring全家桶开发) 一、Spring Spring 是分层的JavaSE/EE应用 full-stack 轻量级开源框架，以 IoC（Inverse Of Control：反转控制） 和 AOP（Aspect Oriented Programming：面向切面编程） 为内核 提供了展现层 Spring MVC 和持久层 Spring JDBCTemplate 以及业务层事务管理等众多的企业级应用技术，还能整合开源世界众多著名的第三方框架和类库，逐渐成为使用最多的JavaEE企业应用开源框架 官网：https://spring.io/ Spring 的核心 IOC：把对象的创建权交给Spring，让Spring帮我们创建并且管理对象 DI：给对象的属性注入值 AOP：解决了一些面向对象编程不方便解决的问题 Spring 的优点 方便解耦，简化开发，把对象的创建权全部交给了Spring管理 支持AOP编程 声明式事务 Spring很方便继承各种优秀的框架 Spring还对比较难用的api提供了封装，方便我们使用 1. Spring 程序开发步骤 导入Spring开发的基本包坐标（Maven依赖） &lt;!-- https://mvnrepository.com/artifact/org.springframework/spring-context --&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-context&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; 编写Dao接口和实现类（Bean） public interface UserDao { public abstract void save(); } public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println(&quot;save方法&quot;); } } 创建Spring核心配置文件（applicationContext.xml） 在Spring配置文件中配置（bean） &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&gt; &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.dao.impl.UserDaoImpl&quot;&gt;&lt;/bean&gt; &lt;/beans&gt; 使用Spring的API获得Bean实例（ new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;)） public class UserDaoDemo { public static void main(String[] args) { ApplicationContext app = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;); UserDao userDao = (UserDao) app.getBean(&quot;userDao&quot;); userDao.save(); } } 2. Spring 配置文件 ①. 基本属性 id：Bean实例在Spring容器的唯一标识 class：Bean的全限定名称 ②. 范围配置 scope：对象的作用范围 singleton：默认值，单例的 prototype：多例的 request：Web项目中，Spring创建一个Bean对象，将对象存入到request域中 session：Web项目中，Spring创建一个Bean对象，将对象存入session域中 global session：Web项目中，应用在Portlet环境，如果没有Portlet环境，那么globalSession相当于session 当scope的取值为singleton时 Bean的实例化个数：1个 Bean的实例化时机：当Spring核心配置文件被加载时，实例化配置的Bean实例 Bean的生命周期 对象创建：当应用加载，创建容器时，对象就被创建了 对象运行：只要容器在，对象一直活着 对象销毁：当应用卸载，销毁容器时，对象就被销毁了 当scope的取值为prototype时 Bean的实例化个数：多个 Bean的实例化时机：当调用getBean()方法时实例化Bean 对象创建：当使用对象时，创建新的对象实例 对象运行：只要对象在使用中，就一直活着 对象销毁：当对象长时间不用时，被Java的垃圾回收器回收了 ③. 生命周期配置 init-method：指定类中的初始化方法名称 destory-method：指定类中销毁方法名称 ④. Bean实例化三种方式 无参构造方法实例化 &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.dao.impl.UserDaoImpl&quot;&gt;&lt;/bean&gt; 工厂静态方法实例化 &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.factory.StaticFactory&quot; factory-method=&quot;getUserDao&quot;&gt;&lt;/bean&gt; 工厂实例方法实例化 &lt;bean id=&quot;factory&quot; class=&quot;com.zhiyuan.factory.DynamicFactory&quot;&gt;&lt;/bean&gt; &lt;bean id=&quot;userDao&quot; factory-bean=&quot;factory&quot; factory-method=&quot;getUserDao&quot;&gt;&lt;/bean&gt; ⑤. Bean的依赖注入 程序最终使用的是UserService，而UserService和UserDao都在Spring容器中，所以可以在Spring容器中，将UserDao设置到UserService的内部，这就叫UserService需要UserDao的依赖注入 依赖注入：Dependency Injection（IOC的具体实现） 1. set方法注入 public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao){ this.userDao = userDao; } public void save(){ userDao.save(); } } &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.dao.impl.UserDaoImpl&quot;&gt;&lt;/bean&gt; &lt;bean id=&quot;userService&quot; class=&quot;com.zhiyuan.service.impl.UserServiceImpl&quot;&gt; &lt;property name=&quot;userDao&quot; ref=&quot;userDao&quot;&gt;&lt;/property&gt; &lt;/bean&gt; 简化set方法注入 引入命名空间 xmlns:p=&quot;http://www.springframework.org/schema/p&quot; 进行依赖注入 &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.dao.impl.UserDaoImpl&quot;&gt;&lt;/bean&gt; &lt;bean id=&quot;userService&quot; class=&quot;com.zhiyuan.service.impl.UserServiceImpl&quot; p:userDao-ref=&quot;userDao&quot;&gt;&lt;/bean&gt; 2. 构造方法注入 public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } public UserServiceImpl() {} public void save(){ userDao.save(); } } &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.dao.impl.UserDaoImpl&quot;&gt;&lt;/bean&gt; &lt;bean id=&quot;userService&quot; class=&quot;com.zhiyuan.service.impl.UserServiceImpl&quot;&gt; &lt;constructor-arg name=&quot;userDao&quot; ref=&quot;userDao&quot;&gt;&lt;/constructor-arg&gt; &lt;/bean&gt; 3. 普通数据类型注入 public class UserDaoImpl implements UserDao { private String username; private int age; @Override public void save() { System.out.println(&quot;save方法&quot;); System.out.println(username+&quot;-&quot;+age); } public void setUsername(String username) { this.username = username; } public void setAge(int age) { this.age = age; } } &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.dao.impl.UserDaoImpl&quot;&gt; &lt;property name=&quot;age&quot; value=&quot;69&quot;&gt;&lt;/property&gt; &lt;property name=&quot;username&quot; value=&quot;张三&quot;&gt;&lt;/property&gt; &lt;/bean&gt; 4. 集合数据类型注入 &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.dao.impl.UserDaoImpl&quot;&gt; &lt;property name=&quot;age&quot; value=&quot;69&quot;&gt;&lt;/property&gt; &lt;property name=&quot;username&quot; value=&quot;张三&quot;&gt;&lt;/property&gt; &lt;property name=&quot;scoreMap&quot;&gt; &lt;map&gt; &lt;entry key=&quot;k1&quot; value=&quot;v1&quot;&gt;&lt;/entry&gt; &lt;entry key=&quot;k2&quot; value=&quot;v2&quot;&gt;&lt;/entry&gt; &lt;/map&gt; &lt;/property&gt; &lt;property name=&quot;list&quot;&gt; &lt;list&gt; &lt;value&gt;v1&lt;/value&gt; &lt;value&gt;v2&lt;/value&gt; &lt;ref bean=&quot;beanName&quot;&gt;&lt;/ref&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; ⑥. 引入其他配置文件 在实际开发中，Spring的配置内容非常多，这就导致Spring配置文件很繁杂体积很大，所以Spring配置文件支持通过import标签引入外部其他的配置文件 &lt;import resource=&quot;applicationContext-xxx.xml&quot; /&gt; 3. ApplicationContext继承体系 ①. ApplicationContext 接口类型，代表应用上下文，可以通过其实例获得Spring容器中的Bean对象 实现类 ClassPathXmlApplicationContext 从类的根路径下加载配置文件（推荐） FileSystemXmlApplicationContext 从磁盘路径中加载配置文件，配置文件可以在任意位置 AnnotationConfigApplicationContext 当使用注解配置容器对象时，需要使用此类来创建Spring容器，它用来读取注解 4. 常用 API ①. getBean()方法 getBean(String name)：获取指定类名称的Bean对象 getBean(Class&lt;T&gt; requiredType)：获取指定类类型的Bean对象 5. Spring配置数据源 ①. 数据源（数据库连接池：DataSource） DBCP C3P0 Druid ②. 数据库连接池的配置 导入数据库相关依赖 &lt;dependency&gt; &lt;groupId&gt;mysql&lt;/groupId&gt; &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;version&gt;8.0.21&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.alibaba&lt;/groupId&gt; &lt;artifactId&gt;druid&lt;/artifactId&gt; &lt;version&gt;1.2.4&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.mchange&lt;/groupId&gt; &lt;artifactId&gt;c3p0&lt;/artifactId&gt; &lt;version&gt;0.9.5.5&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-context&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; Spring配置数据库连接池 &lt;bean id=&quot;dataSource&quot; class=&quot;com.mchange.v2.c3p0.ComboPooledDataSource&quot;&gt; &lt;property name=&quot;driverClass&quot; value=&quot;com.mysql.cj.jdbc.Driver&quot;&gt;&lt;/property&gt; &lt;property name=&quot;jdbcUrl&quot; value=&quot;jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&quot;&gt;&lt;/property&gt; &lt;property name=&quot;user&quot; value=&quot;root&quot;&gt;&lt;/property&gt; &lt;property name=&quot;password&quot; value=&quot;123456&quot;&gt;&lt;/property&gt; &lt;/bean&gt; &lt;bean id=&quot;dataSource&quot; class=&quot;com.alibaba.druid.pool.DruidDataSource&quot;&gt; &lt;property name=&quot;driverClassName&quot; value=&quot;com.mysql.cj.jdbc.Driver&quot;&gt;&lt;/property&gt; &lt;property name=&quot;url&quot; value=&quot;jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&quot;&gt;&lt;/property&gt; &lt;property name=&quot;username&quot; value=&quot;root&quot;&gt;&lt;/property&gt; &lt;property name=&quot;password&quot; value=&quot;123456&quot;&gt;&lt;/property&gt; &lt;/bean&gt; 代码实现 public class DataSourceDemo1 { public static void main(String[] args) throws Exception { // ComboPooledDataSource ds = new ComboPooledDataSource(); // Connection conn = ds.getConnection(); // String sql = &quot;select * from user&quot;; // PreparedStatement pstat = conn.prepareStatement(sql); // ResultSet rs = pstat.executeQuery(); // while(rs.next()){ // System.out.println(rs.getInt(&quot;id&quot;)); // } // ApplicationContext app = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;); // DataSource ds = (DataSource)app.getBean(&quot;dataSource&quot;); // Connection conn = ds.getConnection(); // String sql = &quot;select * from user&quot;; // PreparedStatement pstat = conn.prepareStatement(sql); // ResultSet rs = pstat.executeQuery(); // while(rs.next()){ // System.out.println(rs.getInt(&quot;id&quot;)); // } // DruidDataSource ds = new DruidDataSource(); // ds.setDriverClassName(&quot;com.mysql.cj.jdbc.Driver&quot;); // ds.setUrl(&quot;jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&quot;); // ds.setUsername(&quot;root&quot;); // ds.setPassword(&quot;123456&quot;); // DruidPooledConnection conn = ds.getConnection(); // String sql = &quot;select * from user&quot;; // PreparedStatement pstat = conn.prepareStatement(sql); // ResultSet rs = pstat.executeQuery(); // while(rs.next()){ // System.out.println(rs.getInt(&quot;id&quot;)); // } ApplicationContext app = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;); DataSource ds = (DataSource)app.getBean(&quot;dataSource&quot;); Connection conn = ds.getConnection(); String sql = &quot;select * from user&quot;; PreparedStatement pstat = conn.prepareStatement(sql); ResultSet rs = pstat.executeQuery(); while(rs.next()){ System.out.println(rs.getInt(&quot;id&quot;)); } } } public class DataSourceDemo1 { public static void main(String[] args) throws Exception { // ComboPooledDataSource ds = new ComboPooledDataSource(); // Connection conn = ds.getConnection(); // String sql = &quot;select * from user&quot;; // PreparedStatement pstat = conn.prepareStatement(sql); // ResultSet rs = pstat.executeQuery(); // while(rs.next()){ // System.out.println(rs.getInt(&quot;id&quot;)); // } ApplicationContext app = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;); DataSource ds = (DataSource)app.getBean(&quot;dataSource&quot;); Connection conn = ds.getConnection(); String sql = &quot;select * from user&quot;; PreparedStatement pstat = conn.prepareStatement(sql); ResultSet rs = pstat.executeQuery(); while(rs.next()){ System.out.println(rs.getInt(&quot;id&quot;)); } } } ③. 数据库连接池抽取配置文件 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&gt; &lt;context:property-placeholder location=&quot;classpath:druid.properties&quot;/&gt; &lt;bean id=&quot;dataSource&quot; class=&quot;com.alibaba.druid.pool.DruidDataSource&quot;&gt; &lt;property name=&quot;driverClassName&quot; value=&quot;${driverClassName}&quot;/&gt; &lt;property name=&quot;url&quot; value=&quot;${url}&quot;/&gt; &lt;property name=&quot;username&quot; value=&quot;${username}&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;${password}&quot;/&gt; &lt;property name=&quot;initialSize&quot; value=&quot;${initialSize}&quot;/&gt; &lt;property name=&quot;maxActive&quot; value=&quot;${maxActive}&quot;/&gt; &lt;property name=&quot;maxWait&quot; value=&quot;${maxWait}&quot;/&gt; &lt;/bean&gt; &lt;/beans&gt; DataSource dataSource = (DataSource)app.getBean(&quot;dataSource&quot;); try { Connection conn = dataSource.getConnection(); System.out.println(conn); } catch (SQLException throwables) { throwables.printStackTrace(); } 这里会产生一个严重错误，产生原因是，在applicationContext.xml中，直接使用username引入的用户名，并非是我们配置文件中的用户名username，排查发现当我们直接给用户名可以正常工作，但是使用{username}引入的用户名，并非是我们配置文件中的用户名username，排查发现当我们直接给用户名可以正常工作，但是使用username引入的用户名，并非是我们配置文件中的用户名username，排查发现当我们直接给用户名可以正常工作，但是使用{}引入变量就会造成错误，所以得出一个结论，我们使用${username}调用配置文件的值时，spring会先从默认系统变量中寻找，找到了直接使用默认的，不会继续寻找配置文件中的，被劫糊了，而系统环境变量与实际的数据库username，而两者编码和内容都不一致，导致连接数据库失败 解决办法，不要在配置文件中适用username属性，虽然druid连接池默认应该是username，但是这里使用spring加载，为了避免spring的混乱，所以还是修改一下druid配置文件中username的名字，改成dbusername或者其他，然后在spring配置文件中引入${dbusername}即可 driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai dbusername=root # 将username改为dbusername password=123456 initialSize=5 maxActive=10 maxWait=3000 6. Spring注解开发 好处：类比web3.0之前的web开发（配置文件形式的）和web3.0及以后的web开发（注解形式的） 原因：Spring是轻代码重配置的框架，我们需要写的代码其实并不是很多，配置的文件那才是相当的多，配置繁多，影响开发效率，所以注解开发是一种简化开发的解决方案，注解代替xml配置文件即可以简化配置，又可以提高开发效率，何乐而不为 ①. Spring原始注解 注解名称 适用范围 注解说明 @Component 用在所有类上 实例化JavaBean @Controller 用在web层类上 实例化JavaBean（语义化web层） @Service 用在service层类上 实例化JavaBean（语义化service层） @Repository 用在dao层类上 实例化JavaBean（语义化dao层） @Autowired 用在字段上 根据类型进行依赖注入 @Qualifier 结合@Autowired，用在字段上 根据名称进行依赖注入 @Resource 等同于@Autowired+@Qualifier，用在字段上 按照名称进行依赖注入 @Value 用在字段上，一般用在使用${}引入数据库配置文件相关属性上 注入普通属性值 @Scope 用在类上，指定实例化模式是 多例 或 单例，prototype 或 singleton 标注JavaBean作用范围 @PostConstruct 用在方法上，指定JavaBean对象的初始化方法init-method 标注JavaBean的初始化方法 @PreDestory 用在方法上，指定JavaBean对象的销毁方法destory-method 标注JavaBean的销毁方法 注意：使用注解开发时，需要在applicationContext.xml中配置组件扫描 作用：指定哪个包及其子包下的JavaBean需要进行扫描，需要扫描的注解对应的类，字段和方法 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&gt; &lt;context:component-scan base-package=&quot;com.zhiyuan&quot;/&gt; &lt;/beans&gt; ②. Spring新注解 虽然原始注解解决了原来很多问题，但依然有些配置问题无法解决，于是Spring新注解出现了，它采用了一种使用类来描述配置文件的方法来提升我们的工作效率，配置文件烦杂，并不意味着我们不再需要配置文件，所以引入了用类来表示配置文件的解决方案 注解名称 适用范围 注解说明 @Configuration 用在类上 指定当前类为Spring配置类，当创建容器时会从该类上加载注释 @ComponentScan 用在类上 指定Spring在初始化容器时扫描的包，作用等同于&lt;context:component-scan base-package=&quot;com.zhiyuan&quot;/&gt; @Bean 用在方法上 标注将该方法的返回值存储到Spring容器中 @PropertySource 用于加载 .properties配置文件中的配置信息 @Import 用于导入其他配置类 //标志本类为Spring的核心配置类 @Configuration //设置组件扫描路径（JavaBean的扫描路径），作用等同于&lt;context:component-scan base-package=&quot;com.zhiyuan&quot;/&gt; @ComponentScan(&quot;com.zhiyuan&quot;) //引入配置文件 @PropertySource(&quot;classpath:druid.properties&quot;) public class DruidConfig { @Value(&quot;${driverClassName}&quot;) private String driver; @Value(&quot;${url}&quot;) private String url; @Value(&quot;${dbusername}&quot;) private String username; @Value(&quot;${password}&quot;) private String password; @Value(&quot;${initialSize}&quot;) private int initialSize; @Value(&quot;${maxActive}&quot;) private int maxActive; @Value(&quot;${maxWait}&quot;) private int maxWait; @Bean(&quot;dataSource&quot;) public DataSource getDataSource(){ Properties pros = new Properties(); DataSource ds = null; try { pros.setProperty(&quot;driverClassName&quot;,driver); pros.setProperty(&quot;url&quot;,url); pros.setProperty(&quot;username&quot;,username); pros.setProperty(&quot;password&quot;,password); pros.setProperty(&quot;initialSize&quot;,String.valueOf(initialSize)); pros.setProperty(&quot;maxActive&quot;, String.valueOf(maxActive)); pros.setProperty(&quot;maxWait&quot;, String.valueOf(maxWait)); ds = DruidDataSourceFactory.createDataSource(pros); } catch (Exception e) { e.printStackTrace(); } return ds; } } //标志本类为Spring的核心配置类 @Configuration //设置组件扫描路径（JavaBean的扫描路径），作用等同于&lt;context:component-scan base-package=&quot;com.zhiyuan&quot;/&gt; @ComponentScan(&quot;com.zhiyuan&quot;) //引入配置文件 @PropertySource(&quot;classpath:druid.properties&quot;) //加入其它配置文件，作用等同于&lt;import resource=&quot;applicationContext-xxx.xml&quot; /&gt; @Import(DruidConfig.class) public class SpringConfiguration { } public class ConfigTest { @Test public void test1(){ ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfiguration.class); DataSource dataSource = (DataSource) app.getBean(&quot;dataSource&quot;); try { Connection conn = dataSource.getConnection(); System.out.println(conn); } catch (SQLException throwables) { throwables.printStackTrace(); } } } 正常输出对象的hash地址值 ③. Spring集成Jnuit 导入SpringTest的依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-test&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; 使用注解 @RunWith(SpringJUnit4ClassRunner.class) 替换运来的运行期 使用 @ContextConfiguration(classes = {SpringConfiguration.class}) 指定配置文件或配置类 使用 @Autowired 注入需要测试的对象 创建测试方法进行测试 代码实现 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfiguration.class}) public class SpringJunitTest { @Autowired private DataSource dataSource; @Test public void test1(){ try { Connection conn = dataSource.getConnection(); System.out.println(conn); } catch (SQLException throwables) { throwables.printStackTrace(); } } } 7. AOP &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-aop&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;cglib&lt;/groupId&gt; &lt;artifactId&gt;cglib&lt;/artifactId&gt; &lt;version&gt;3.3.0&lt;/version&gt; &lt;/dependency&gt; &lt;!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --&gt; &lt;dependency&gt; &lt;groupId&gt;org.aspectj&lt;/groupId&gt; &lt;artifactId&gt;aspectjweaver&lt;/artifactId&gt; &lt;version&gt;1.9.6&lt;/version&gt; &lt;/dependency&gt; ①. 概述 AOP是Aspect Oriented Programming的缩写，意思是面向切面编程，是通过预编译方式和运行期动态代理实现程序功能统一维护的一种技术 ②. 作用 AOP是OOP的延续，是Spring框架中一个重要内容，是函数式编程的一种衍生，利用AOP可以对业务逻辑各个部分进行隔离，从而使得业务逻辑各部分之间的耦合度降低，提高程序的可用性，同时提高了开发效率 在不修改源码的情况下对方法的功能进行增强 优点：减少重复代码，提高开发效率，便于维护 ③. AOP底层实现 动态代理技术 JDK代理：基于接口的动态代理对象 CGLIB代理：基于父类的动态代理技术 ④. 相关概念 Target（目标对象）：代理的目标对象 Proxy（代理）：一个类被AOP增强后，就产生一个结果代理类 Joinpoint（连接点）：被拦截到的点，在spring中指的是方法，因为spring只支持方法类型的连接点 Pointcut（切入点）：指我们要对哪些Joinpoint进行拦截定义 Advice（通知/增强）：指拦截到Joinpoint之后所要做的增强 Aspect（切面）：是切入点和增强的结合 Weaving（织入）：切入点和增强结合的过程 概念性的东西着重归纳出自己的见解，而不是死记硬背，像这种恶心的概念，也只有记笔记的时候，才会出现 ⑤. XML配置AOP详解 切点表达式 语法：execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 访问修饰符可以省略 返回值类型，包名，类名，方法名可以使用 * 代表任意 包名与类名之间一个点 . 代表当前包下的类，两个点 ..表示当前包及其子包下的类 参数列表可以使用两个点 .. 表示任意个数，任意类型的参数列表 增强类型 语法：&lt;aop:增强类型 method=&quot;切面类中方法名&quot; pointcut=&quot;切点表达式&quot; /&gt; 增强类型 前置增强：&lt;aop:before&gt;：在切点方法执行之前执行 后置增强：&lt;aop:after-returning&gt;：在切点方法执行之后执行 环绕增强：&lt;aop:around&gt;：在切点方法之前之后均执行 异常抛出增强：&lt;aop:throwing&gt;：当增强方法出现异常时执行 最终增强：&lt;aop:after&gt;：在所有增强方法执行完毕时执行 代码实现 public class Target { public void show(){ System.out.println(&quot;show方法&quot;); } } public class MyAspect { public void before(){ System.out.println(&quot;前置增强方法&quot;); } public void afterReturning(){ System.out.println(&quot;后置增强方法&quot;); } public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println(&quot;前置增强方法&quot;); Object proceed = pjp.proceed(); System.out.println(&quot;后置增强方法&quot;); return proceed; } public void afterThrowing(){ System.out.println(&quot;增强方法出现异常&quot;); } public void after(){ System.out.println(&quot;最终增强&quot;); } } &lt;bean id=&quot;target&quot; class=&quot;com.zhiyuan.proxy.aop.Target&quot;/&gt; &lt;bean id=&quot;aspect&quot; class=&quot;com.zhiyuan.proxy.aop.MyAspect&quot;/&gt; &lt;aop:config&gt; &lt;aop:aspect ref=&quot;aspect&quot;&gt; &lt;aop:before method=&quot;before&quot; pointcut=&quot;execution(* com.zhiyuan.proxy.aop.*.*(..))&quot;/&gt; &lt;aop:after-returning method=&quot;afterReturning&quot; pointcut=&quot;execution(* com.zhiyuan.proxy.aop.*.*(..))&quot;/&gt; &lt;aop:around method=&quot;around&quot; pointcut=&quot;execution(* com.zhiyuan.proxy.aop.*.*(..))&quot;/&gt; &lt;aop:after-throwing method=&quot;afterThrowing&quot; pointcut=&quot;execution(* com.zhiyuan.proxy.aop.*.*(..))&quot;/&gt; &lt;aop:after method=&quot;after&quot; pointcut=&quot;execution(* com.zhiyuan.proxy.aop.*.*(..))&quot;/&gt; &lt;/aop:aspect&gt; &lt;/aop:config&gt; ⑥. 切点表达式抽取 当多个增强的切点表达式相同时，可以将切点表达式进行抽取，在增强中使用 pointcut-ref 属性替代 pointcut 属性来引用抽取后的切点表达式 &lt;bean id=&quot;target&quot; class=&quot;com.zhiyuan.proxy.aop.Target&quot;/&gt; &lt;bean id=&quot;aspect&quot; class=&quot;com.zhiyuan.proxy.aop.MyAspect&quot;/&gt; &lt;aop:config&gt; &lt;aop:aspect ref=&quot;aspect&quot;&gt; &lt;aop:pointcut id=&quot;myPointcut&quot; expression=&quot;execution(* com.zhiyuan.proxy.aop.*.*(..))&quot;/&gt; &lt;aop:before method=&quot;before&quot; pointcut-ref=&quot;myPointcut&quot;/&gt; &lt;aop:after-returning method=&quot;afterReturning&quot; pointcut-ref=&quot;myPointcut&quot;/&gt; &lt;aop:around method=&quot;around&quot; pointcut-ref=&quot;myPointcut&quot;/&gt; &lt;aop:after-throwing method=&quot;afterThrowing&quot; pointcut-ref=&quot;myPointcut&quot;/&gt; &lt;aop:after method=&quot;after&quot; pointcut-ref=&quot;myPointcut&quot;/&gt; &lt;/aop:aspect&gt; &lt;/aop:config&gt; ⑦. 注解配置AOP 配置步骤 使用 @Aspect 标注切面类 使用 @增强类型 标注增强方法 在配置文件中配置aop自动代理 &lt;aop:aspectj-autoproxy/&gt; 注解增强类型 @Before：用于配置前置增强，指定增强的方法在切入点之前执行 @AfterReturning：用于配置后置增强，指定增强的方法在切入点方法之后执行 @Around：用于配置环绕增强，指定增强的方法在出现异常时执行 @AfterThrowing：用于配置异常抛出通知，指定增强的方法在出现异常时执行 @After：用于配置最终通知，无论增强方法执行是否有异常都会执行 代码实现 public interface TargetInterface { public abstract void show(); } @Component(&quot;target&quot;) public class Target implements TargetInterface{ @Override public void show(){ System.out.println(&quot;show方法&quot;); } } @Component(&quot;myAspect&quot;) @Aspect public class MyAspect { @Before(&quot;execution(* com.zhiyuan.proxy.anno.*.*(..))&quot;) public void before(){ System.out.println(&quot;前置增强方法&quot;); } @AfterReturning(&quot;execution(* com.zhiyuan.proxy.anno.*.*(..))&quot;) public void afterReturning(){ System.out.println(&quot;后置增强方法&quot;); } @Around(&quot;execution(* com.zhiyuan.proxy.anno.*.*(..))&quot;) public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println(&quot;前置增强方法&quot;); Object proceed = pjp.proceed(); System.out.println(&quot;后置增强方法&quot;); return proceed; } @AfterThrowing(&quot;execution(* com.zhiyuan.proxy.anno.*.*(..))&quot;) public void afterThrowing(){ System.out.println(&quot;增强方法出现异常&quot;); } @After(&quot;execution(* com.zhiyuan.proxy.anno.*.*(..))&quot;) public void after(){ System.out.println(&quot;最终增强&quot;); } } &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:aop=&quot;http://www.springframework.org/schema/aop&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&gt; &lt;!--开启组件扫描--&gt; &lt;context:component-scan base-package=&quot;com.zhiyuan.proxy.anno&quot;/&gt; &lt;!--设置AOP自动代理--&gt; &lt;aop:aspectj-autoproxy/&gt; &lt;/beans&gt; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(&quot;classpath:applicationContext-anno.xml&quot;) public class AnnoTest { @Autowired private TargetInterface target; @Test public void test1(){ target.show(); } } 前置增强方法 前置增强方法 show方法 后置增强方法 最终增强 后置增强方法 抽取切点表达式 在切面内定义方法，在该方法上使用 @Pointcut 注解定义切点表达式，然后在增强注解中进行引用 代码实现 @Component(&quot;myAspect&quot;) @Aspect public class MyAspect { @Before(&quot;MyAspect.pointcut()&quot;) public void before(){ System.out.println(&quot;前置增强方法&quot;); } @AfterReturning(&quot;pointcut()&quot;) public void afterReturning(){ System.out.println(&quot;后置增强方法&quot;); } @Around(&quot;pointcut()&quot;) public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println(&quot;前置增强方法&quot;); Object proceed = pjp.proceed(); System.out.println(&quot;后置增强方法&quot;); return proceed; } @AfterThrowing(&quot;pointcut()&quot;) public void afterThrowing(){ System.out.println(&quot;增强方法出现异常&quot;); } @After(&quot;pointcut()&quot;) public void after(){ System.out.println(&quot;最终增强&quot;); } @Pointcut(&quot;execution(* com.zhiyuan.proxy.anno.*.*(..))&quot;) public void pointcut(){} } 8. SpringJdbcTemplate ①. 导入依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-tx&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; ②. xml配置 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd &quot;&gt; &lt;!--加载配置文件--&gt; &lt;context:property-placeholder location=&quot;classpath:druid.properties&quot;/&gt; &lt;!--配置DataSource--&gt; &lt;bean id=&quot;dataSource&quot; class=&quot;com.alibaba.druid.pool.DruidDataSource&quot;&gt; &lt;property name=&quot;driverClassName&quot; value=&quot;${driverClassName}&quot;/&gt; &lt;property name=&quot;url&quot; value=&quot;${url}&quot;/&gt; &lt;property name=&quot;username&quot; value=&quot;${dbusername}&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;${password}&quot;/&gt; &lt;/bean&gt; &lt;!--配置JdbcTemplate--&gt; &lt;bean id=&quot;jdbcTemplate&quot; class=&quot;org.springframework.jdbc.core.JdbcTemplate&quot;&gt; &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt; &lt;/bean&gt; &lt;/beans&gt; public class JdbcTemplateTest { @Test public void test1(){ ApplicationContext app = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;); JdbcTemplate jdbcTemplate = (JdbcTemplate)app.getBean(&quot;jdbcTemplate&quot;); List&lt;User&gt; list = jdbcTemplate.query(&quot;select * from user&quot;, new BeanPropertyRowMapper&lt;User&gt;(User.class)); for (User user : list) { System.out.println(user); } } } 9. Spring的事务控制 ①. 编程式事务控制 PlatformTransactionManager ：Spring的事务管理器 TransactionStatus getTransaction(TransactionDefination defination)：获取事务状态信息 void commit(TransactionStatus status)：提交事务 void rollback(TransactionStatus status)：回滚事务 TransactionDefination：事务信息对象 int getIsolationLevel()：获取事务的隔离级别 int getPropogationBehavior：获取事务的传播行为 int getTimeout()：获取超时时间 boolean isReadOnly()：是否只读 事务隔离级别 ISOLATION_DEFAULT：默认 ISOLATION_READ_UNCOMMITTED：读未提交 ISOLATION_READ_COMMITTED：读已提交 ISOLATION_REPEATABLE_READ：可重复读 ISOLATION_SERIALIZABLE：串行化 事务传播行为 REQUIRED：如果当时没有事务，就新建一个事务，如果已存在一个事务中，加入到这个事务中，一般选择这一项（默认值） SUPPORTS：支持当前事务，如果当前没有事务，就以非事务方式执行 MANDATORY：使用当前的事务，如果当前没有事务，就抛出异常 REQUES_NEW：新建事务，如果当前在事务中，就把当前事务挂起 NOT_SUPPORTED：以非事务方式执行操作，如果当前存在事务，就把当前事务挂起 NEVER：以非事务方式运行，如果当前存在事务，抛出异常 NESTED：如果当前存在事务，则在嵌套事务内执行，如果当前没有实物，则执行 REQUIRED 类似的操作 超时时间：默认值为-1，没有超时限制，单位为秒可设置超时时间 是否只读：建议查询时设置为只读 TransactionStatus：事务状态信息 boolean hasSavepoint()：是否存储回滚点 boolean isCompleted()：事务是否完成 boolean isNewTransaction()：是否是新事务 boolean isRollbackOnly()：事务是否回滚 ②. 声明式事务控制 Spring声明式事务控制底层就是AOP 基于xml配置实现 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xmlns:aop=&quot;http://www.springframework.org/schema/aop&quot; xmlns:tx=&quot;http://www.springframework.org/schema/tx&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd&quot;&gt; &lt;!--加载配置文件--&gt; &lt;context:property-placeholder location=&quot;classpath:druid.properties&quot;/&gt; &lt;!--配置DataSource--&gt; &lt;bean id=&quot;dataSource&quot; class=&quot;com.alibaba.druid.pool.DruidDataSource&quot;&gt; &lt;property name=&quot;driverClassName&quot; value=&quot;${driverClassName}&quot;/&gt; &lt;property name=&quot;url&quot; value=&quot;${url}&quot;/&gt; &lt;property name=&quot;username&quot; value=&quot;${dbusername}&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;${password}&quot;/&gt; &lt;/bean&gt; &lt;!--配置JdbcTemplate--&gt; &lt;bean id=&quot;jdbcTemplate&quot; class=&quot;org.springframework.jdbc.core.JdbcTemplate&quot;&gt; &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt; &lt;/bean&gt; &lt;bean id=&quot;userDao&quot; class=&quot;com.zhiyuan.dao.impl.UserDaoImpl&quot;&gt; &lt;property name=&quot;jdbcTemplate&quot; ref=&quot;jdbcTemplate&quot;/&gt; &lt;/bean&gt; &lt;bean id=&quot;userService&quot; class=&quot;com.zhiyuan.service.impl.UserServiceImpl&quot;&gt; &lt;property name=&quot;userDao&quot; ref=&quot;userDao&quot;/&gt; &lt;/bean&gt; &lt;!--配置平台事务管理对象--&gt; &lt;bean id=&quot;transactionManager&quot; class=&quot;org.springframework.jdbc.datasource.DataSourceTransactionManager&quot;&gt; &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt; &lt;/bean&gt; &lt;!--增强事务--&gt; &lt;tx:advice id=&quot;txAdvice&quot; transaction-manager=&quot;transactionManager&quot;&gt; &lt;tx:attributes&gt; &lt;tx:method name=&quot;transfer&quot; isolation=&quot;REPEATABLE_READ&quot; propagation=&quot;REQUIRED&quot; read-only=&quot;false&quot;/&gt; &lt;tx:method name=&quot;*&quot; isolation=&quot;DEFAULT&quot; propagation=&quot;REQUIRED&quot; timeout=&quot;-1&quot; read-only=&quot;false&quot;/&gt; &lt;/tx:attributes&gt; &lt;/tx:advice&gt; &lt;!--配置事务的织入--&gt; &lt;aop:config&gt; &lt;aop:advisor advice-ref=&quot;txAdvice&quot; pointcut=&quot;execution(* com.zhiyuan.service.impl.*.*(..))&quot;/&gt; &lt;/aop:config&gt; &lt;/beans&gt; 基于注解配置实现 使用 @Transactional 在需要进行事务控制的类或是方法上修饰，注解可用的属性同xml的配置方式 注解使用在类上，那么该类下所有方法都是用同一套注解参数配置 使用在方法上，不同的方法可以采用不同的事务参数配置 xml配置文件中要开启事务的注解驱动 &lt;tx:annotation-driven&gt; 事务控制配置建议使用xml配置 10. Spring与web环境的集成 ①. ApplicationContext的获取 使用 ServletContextListener 监听web应用的启动，在 ServletContext 被创建时，创建 ApplicationContext 对象并存储到 ServletContext 域中，这样就可以在当前项目的任意位置获取到Spring容器 Spring提供了一个监听器 ContextLoaderListener 就是对上述功能的封装，该监听器内部加载Spring配置文件，创建了Spring容器对象，并存储到ServletContext域中，提供一个工具类供使用者获得Spring容器对象 使用步骤 导入 spring-web 坐标 在 web.xml 中配置 ContextLoaderListener 监听器 使用 WebApplicationContextUtils 获得应用上下文对象 ApplicationContext 代码实现 &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-web&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;web-app xmlns=&quot;http://xmlns.jcp.org/xml/ns/javaee&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd&quot; version=&quot;4.0&quot;&gt; &lt;context-param&gt; &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt; &lt;param-value&gt;classpath:applicationContext.xml&lt;/param-value&gt; &lt;/context-param&gt; &lt;listener&gt; &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt; &lt;/listener&gt; &lt;/web-app&gt; ServletContext servletContext = this.getServletContext(); ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext); 二、SpringMVC SpringMVC是一种基于Java实现MVC设计模式的请求驱动类型的轻量级web框架，属于SpringFrameWork的后续产品，已经融合在 SpringWebFlow 中 它通过一套注解，让一个简单的Java类成为处理请求的控制器，而无序实现任何接口，同时它还支持RESTful 编程风格的请求 RestFul请求规范： get：查询数据 post：添加数据 put：修改数据 delete：删除数据 1. 开发步骤 ①. 导入SpringMVC的依赖 ②. 配置SpringMVC核心控制器DispatcherServlet ③. 编写Controller业务和View页面 ④. 将Controller使用注解配置到Spring容器中 ⑤. 配置spring-mvc.xml文件（配置组件扫描） ⑥. 客户端发起请求测试 2. 代码实现 导入SpringMVC依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; 配置Servlet &lt;!--配置SpringMVC的前端控制器--&gt; &lt;servlet&gt; &lt;servlet-name&gt;DispatcherServlet&lt;/servlet-name&gt; &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt; &lt;init-param&gt; &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt; &lt;param-value&gt;classpath:springmvc.xml&lt;/param-value&gt; &lt;/init-param&gt; &lt;load-on-startup&gt;1&lt;/load-on-startup&gt; &lt;/servlet&gt; &lt;servlet-mapping&gt; &lt;servlet-name&gt;DispatcherServlet&lt;/servlet-name&gt; &lt;url-pattern&gt;/&lt;/url-pattern&gt; &lt;/servlet-mapping&gt; 编写Controller @Controller public class UserController extends HttpServlet { @RequestMapping(&quot;/show&quot;) public String show(){ System.out.println(&quot;show地址映射&quot;); return &quot;new.jsp&quot;; } } 配置SpringMVC配置文件组件扫描 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&gt; &lt;context:component-scan base-package=&quot;com.zhiyuan.controller&quot;/&gt; &lt;/beans&gt; 3. 流程图示 4. 执行流程 ①. 用户发送请求到前端控制器DispatcherServlet ②. DispatcherServlet收到请求调用HandlerMapping处理器映射器 ③. 处理器映射器找到具体的处理器（可以根据xml或者注解进行查找），生成处理器对象及处理器拦截器（如果有则生成）一并返回给DispatcherServlet ④. DisptcherServlet调用HandlerAdaptor处理器适配器 ⑤. HandlerAdaptor经过适配器调用具体的处理器（Controller，也叫后端控制器） ⑥. Controller执行完成返回ModelAndView ⑦. HandlerAdaptor将controller执行结ModelAndView返回给DispatcherServlet ⑧. DispatcherServlet将ModelAndView传给ViewReslover视图解析器 ⑨. ViewReslover解析后返回具体View ⑩. DispatcherServlet根据View进行渲染视图（将模型数据填充至视图中），由DispatcherServlet响应用户 5. SpringMVC注解解析 ①. @RequestMapping 用在类上 映射模块路径：请求URL的一级访问目录，如果类上不加，默认为根目录 用在方法上 映射方法路径：请求URL的二级访问目录，与类上的目录组合完成指定方法的访问 属性 value：用于指定请求的URL method：用于指定请求的方式 值建议使用RequestMethod枚举类型 params：用于指定限制请求参数的条件，它支持简单的表达式，要求请求参数的key和value必须和配置的一模一样 6. SpringMVC的xml配置 &lt;!--开启Controller注解扫描--&gt; &lt;context:component-scan base-package=&quot;com.zhiyuan.controller&quot;&gt; &lt;context:include-filter type=&quot;annotation&quot; expression=&quot;org.springframework.stereotype.Controller&quot;/&gt; &lt;/context:component-scan&gt; &lt;!--注册处理器映射器--&gt; &lt;bean id=&quot;handlerMapping&quot; class=&quot;org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping&quot;/&gt; &lt;!--注册处理器适配器--&gt; &lt;bean class=&quot;org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter&quot;&gt; &lt;property name=&quot;messageConverters&quot;&gt; &lt;list&gt; &lt;bean id=&quot;stringConverter&quot; class=&quot;org.springframework.http.converter.StringHttpMessageConverter&quot;&gt; &lt;property name=&quot;supportedMediaTypes&quot;&gt; &lt;list&gt; &lt;value&gt;text/html;charset=UTF-8&lt;/value&gt; &lt;value&gt;application/json;charset=UTF-8&lt;/value&gt; &lt;value&gt;text/plain;charset=UTF-8&lt;/value&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; &lt;bean id=&quot;jsonConverter&quot; class=&quot;org.springframework.http.converter.json.MappingJackson2HttpMessageConverter&quot;&gt; &lt;property name=&quot;supportedMediaTypes&quot;&gt; &lt;list&gt; &lt;value&gt;text/html;charset=UTF-8&lt;/value&gt; &lt;value&gt;application/json;charset=UTF-8&lt;/value&gt; &lt;value&gt;text/plain;charset=UTF-8&lt;/value&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; &lt;!--注册内部视图解析器--&gt; &lt;bean id=&quot;viewResolver&quot; class=&quot;org.springframework.web.servlet.view.InternalResourceViewResolver&quot;&gt; &lt;property name=&quot;prefix&quot; value=&quot;/jsp/&quot;/&gt; &lt;property name=&quot;suffix&quot; value=&quot;.jsp&quot;/&gt; &lt;/bean&gt; 7. SpringMVC的相关组件 前端控制器：DisptcherServlet（SpringMVC提供的分发请求的Servlet，需要我们在web.xml中配置） 处理器映射器：HandlerMapping（SpringMVC提供的映射容器） 处理器适配器：HandlerAdapter（SpringMVC提供的处理器适配器） 处理器：Handler（需要我们写的Controller） 视图解析器：ViewResolver（SpringMVC提供的资源视图解析器） 视图：View 8. SpringMVC数据响应方式 ①. 页面跳转 直接返回字符串：这种方式会将返回的字符串与视图解析器的前后缀拼接后跳转 @RequestMapping(&quot;/test&quot;) public String test(){ return &quot;index.jsp&quot;; } 通过ModelAndView对象返回 @RequestMapping(&quot;/test&quot;) public ModelAndView test(){ ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName(&quot;new&quot;); modelAndView.addObject(&quot;username&quot;,&quot;zhiyuan&quot;); return modelAndView; } @RequestMapping(&quot;/test2&quot;) public ModelAndView test2(ModelAndView modelAndView){ modelAndView.setViewName(&quot;new&quot;); modelAndView.addObject(&quot;username&quot;,&quot;zhiyuan&quot;); return modelAndView; } @RequestMapping(&quot;/test3&quot;) public Model test3(Model model){ model.addAttribute(&quot;username&quot;,&quot;张三&quot;); return model; } @RequestMapping(&quot;/test4&quot;) public String test4(HttpServletRequest request,HttpServletResponse response){ request.setAttribute(&quot;username&quot;,&quot;张三&quot;); return &quot;index.jsp&quot;; } ②. 回写数据 直接返回字符串 @RequestMapping(&quot;/test3&quot;) @ResponseBody public String test3(HttpServletResponse response){ response.setContentType(&quot;text/html;charset=utf-8&quot;); return &quot;直接回写字符串&quot;; } 返回对象或集合 &lt;!--配置处理器映射器--&gt; &lt;bean class=&quot;org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter&quot;&gt; &lt;property name=&quot;messageConverters&quot;&gt; &lt;list&gt; &lt;bean id=&quot;stringConverter&quot; class=&quot;org.springframework.http.converter.StringHttpMessageConverter&quot;&gt; &lt;property name=&quot;supportedMediaTypes&quot;&gt; &lt;list&gt; &lt;value&gt;text/html;charset=UTF-8&lt;/value&gt; &lt;value&gt;application/json;charset=UTF-8&lt;/value&gt; &lt;value&gt;text/plain;charset=UTF-8&lt;/value&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; &lt;bean id=&quot;jsonConverter&quot; class=&quot;org.springframework.http.converter.json.MappingJackson2HttpMessageConverter&quot;&gt; &lt;property name=&quot;supportedMediaTypes&quot;&gt; &lt;list&gt; &lt;value&gt;text/html;charset=UTF-8&lt;/value&gt; &lt;value&gt;application/json;charset=UTF-8&lt;/value&gt; &lt;value&gt;text/plain;charset=UTF-8&lt;/value&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; &lt;!--开启扫描注解驱动--&gt; &lt;mvc:annotation-driven/&gt; @RequestMapping(&quot;/test&quot;) @ResponseBody public Person test(){ Person person = new Person(); person.setAge(25); person.setGender(&quot;male&quot;); person.setName(&quot;张三&quot;); return person; } 9. SpringMVC获得请求数据 ①. 获得请求参数 基本类型参数 POJO类型参数（JavaBean） 数组类型参数 集合类型参数 ②. 获得一个基本类型参数 @RequestMapping(&quot;/test&quot;) @ResponseBody public void test(String username,String age,String gender){ System.out.println(username + &quot;\\t&quot; + age + &quot;\\t&quot; + gender); } 张三 26 male ③. 获得一个POJO类型参数 @RequestMapping(&quot;/test4&quot;) @ResponseBody public void test4(User user){ System.out.println(user); } User{username='张三', gender='male', age=26} @RequestMapping(&quot;/test4&quot;) @ResponseBody public void test4(@RequestBody User user){ System.out.println(user); } { &quot;username&quot;:&quot;zhiyuan&quot;, &quot;gender&quot;:&quot;male&quot;, &quot;age&quot;:26 } User{username='zhiyuan', gender='male', age=26} ④. 获得一个数组类型参数 @RequestMapping(&quot;/test5&quot;) @ResponseBody public void test5(String[] id){ System.out.println(Arrays.asList(id)); } [111, 222] ⑤. 获得一个集合类型参数(一) package com.zhiyuan.domain; import java.util.List; public class VO { private List&lt;User&gt; userList; public List&lt;User&gt; getUserList() { return userList; } public void setUserList(List&lt;User&gt; userList) { this.userList = userList; } @Override public String toString() { final StringBuffer sb = new StringBuffer(&quot;VO{&quot;); sb.append(&quot;userList=&quot;).append(userList); sb.append('}'); return sb.toString(); } } @RequestMapping(&quot;/test6&quot;) @ResponseBody public void test6(VO vo){ System.out.println(vo); } VO{userList=[User{username='??????', gender='male', age=26}, User{username='??????', gender='female', age=22}]} ⑥. 获得一个集合类型参数(二) &lt;%@ page contentType=&quot;text/html;charset=utf-8&quot; language=&quot;java&quot;%&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;Ajax请求&lt;/title&gt; &lt;script src=&quot;${pageContext.request.contextPath}/js/jquery-3.1.1.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;script&gt; var userList = new Array(); userList.push({username:&quot;zhangsan&quot;,age:26}); userList.push({username:&quot;lisi&quot;,age:28}); $.ajax({ type:&quot;POST&quot;, url:&quot;${pageContext.request.contextPath}/test7&quot;, data:JSON.stringify(userList), contentType:&quot;application/json;charset=utf-8&quot; }); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; @RequestMapping(&quot;/test7&quot;) @ResponseBody public void test7(@RequestBody List&lt;User&gt; userList){ System.out.println(userList); } ⑦. 开启静态资源访问 &lt;!--静态资源的访问--&gt; &lt;mvc:resources mapping=&quot;/js/**&quot; location=&quot;/js/&quot;&gt; &lt;mvc:resources mapping=&quot;/img/**&quot; location=&quot;/img/&quot;&gt; &lt;mvc:resources mapping=&quot;/css/**&quot; location=&quot;/css/&quot;&gt; &lt;!--交由tomcat去寻找对应静态资源--&gt; &lt;mvc:default-servlet-handler/&gt; ⑧. 配置全局乱码过滤器 &lt;!--配置全局编码过滤器--&gt; &lt;filter&gt; &lt;filter-name&gt;CharacterEncodingFilter&lt;/filter-name&gt; &lt;filter-class&gt;org.springframework.web.filter.CharacterEncodingFilter&lt;/filter-class&gt; &lt;init-param&gt; &lt;param-name&gt;encoding&lt;/param-name&gt; &lt;param-value&gt;UTF-8&lt;/param-value&gt; &lt;/init-param&gt; &lt;/filter&gt; &lt;filter-mapping&gt; &lt;filter-name&gt;CharacterEncodingFilter&lt;/filter-name&gt; &lt;url-pattern&gt;/*&lt;/url-pattern&gt; &lt;/filter-mapping&gt; ⑨. 参数绑定注解 @RequestMapping(&quot;/test5&quot;) @ResponseBody public void test5(@RequestParam(&quot;id&quot;) String[] ids){ System.out.println(Arrays.asList(ids)); } @RequestParam value：与请求参数名称进行绑定 required：请求参数是否必须携带，默认为true，不携带参数请求URL会报错 defaultValue：请求参数未写时默认的值 10. Restful风格 Restful风格的请求是使用 URL+请求方式 表示一次请求目的，HTTP协议里四个表示操作方式的动词如下 GET：用于获取数据 POST：用户添加数据 PUT：用于修改数据 DELETE：用于删除数据 ①. Restful风格请求参数的获取 @RequestMapping(&quot;/test8/{username}&quot;) @ResponseBody public void test8(@PathVariable(&quot;username&quot;) String username, HttpServletRequest request){ System.out.println(request.getMethod()+&quot;:&quot;+username); } GET:zhangsan POST:zhangsan PUT:zhangsan DELETE:zhangsan 因为每个方法都需要添加 @ResponseBody 所以Spring为我们提供了一个解决方案 @RestController 通过源码可知 @RestController = @Controller + @ResponseBody 11. SpringMVC自定义类型转换器 SpringMVC默认已经提供了一些常用的类型转换器，例如客户端提交的字符串转换成int型进行参数设置 但是并不是所有的数据类型都提供了转换器，没有提供的就需要自定义转换器，来如：日期类型的数据就需要自定义转换器 步骤 定义转换器类实现 Converter 接口 在配置文件中声明转换器 在 &lt;annotation-driven&gt; 中引用转换器 public class DateConverter implements Converter&lt;String,Date&gt;{ public Date convert(String source) { System.out.println(source); Date date = null; try { date = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;).parse(source); } catch (ParseException e) { e.printStackTrace(); } return date; } } &lt;mvc:annotation-driven conversion-service=&quot;conversionService&quot;/&gt; &lt;!--声明转换器--&gt; &lt;bean id=&quot;conversionService&quot; class=&quot;org.springframework.context.support.ConversionServiceFactoryBean&quot;&gt; &lt;property name=&quot;converters&quot;&gt; &lt;list&gt; &lt;bean class=&quot;com.zhiyuan.controller.DateConverter&quot;/&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; @RequestMapping(&quot;/test9&quot;) @ResponseBody public void test9(Date date){ System.out.println(date); } 12. SpringMVC获取请求头数据 ①. @RequestHeader 使用 @RequestHeader 获取请求头信息，相当于web阶段学习的 request.getHeader(name) value：请求头名称 required：是否必须携带某请求头 @RequestMapping(&quot;/test10&quot;) @ResponseBody public void test10(@RequestHeader(value = &quot;User-Agent&quot;,required = true) String userAgent){ System.out.println(userAgent); } ②. @CookieValue 使用 @CookieValue 可以获取指定Cookie的值 value：指定cookie的名称 required：是否必须携带某cookie @RequestMapping(&quot;/test11&quot;) @ResponseBody public void test11(@CookieValue(value = &quot;JSESSIONID&quot;,required = true) String sessionId){ System.out.println(sessionId); } 13. SpringMVC文件上传 ①. 简单上传表单 &lt;form action=&quot;/test12&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt; 用户名：&lt;input type=&quot;text&quot; name=&quot;username&quot;/&gt;&lt;br&gt; 头像：&lt;input type=&quot;file&quot; name=&quot;upload&quot;&gt;&lt;br&gt; &lt;input type=&quot;submit&quot; value=&quot;提交&quot;&gt; &lt;/form&gt; ②. 导入依赖 &lt;dependency&gt; &lt;groupId&gt;commons-io&lt;/groupId&gt; &lt;artifactId&gt;commons-io&lt;/artifactId&gt; &lt;version&gt;2.4&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;commons-fileupload&lt;/groupId&gt; &lt;artifactId&gt;commons-fileupload&lt;/artifactId&gt; &lt;version&gt;1.2.2&lt;/version&gt; &lt;/dependency&gt; ③. 配置文件上传解析器 &lt;!--配置文件上传解析器--&gt; &lt;!--此id必须为multipartResolver，是固定写法，不一样就是错--&gt; &lt;bean id=&quot;multipartResolver&quot; class=&quot;org.springframework.web.multipart.commons.CommonsMultipartResolver&quot;&gt; &lt;!--上传文件编码类型--&gt; &lt;property name=&quot;defaultEncoding&quot; value=&quot;UTF-8&quot;/&gt; &lt;!--上传单个文件大小--&gt; &lt;property name=&quot;maxUploadSizePerFile&quot; value=&quot;5242880&quot;/&gt; &lt;!--上传文件总大小--&gt; &lt;property name=&quot;maxUploadSize&quot; value=&quot;5242880&quot;/&gt; &lt;/bean&gt; ④. 编写文件上传处理代码 @RequestMapping(value = &quot;/test12&quot;,method = RequestMethod.POST) @ResponseBody public void test12(@RequestParam(value = &quot;username&quot;,required = false) String username,@RequestParam(value=&quot;upload&quot;,required=false) MultipartFile upload){ System.out.println(username); System.out.println(upload); String originalFilename = upload.getOriginalFilename(); upload.transferTo(new File(&quot;F:\\\\upload\\\\&quot;+ MyUtils.getUUID() + originalFilename)); } 14. SpringMVC多文件上传 ①. 简单上传表单 &lt;form action=&quot;/test13&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt; 用户名：&lt;input type=&quot;text&quot; name=&quot;username&quot;/&gt;&lt;br&gt; 头像：&lt;input type=&quot;file&quot; name=&quot;upload&quot;&gt;&lt;br&gt; 头像：&lt;input type=&quot;file&quot; name=&quot;upload&quot;&gt;&lt;br&gt; 头像：&lt;input type=&quot;file&quot; name=&quot;upload&quot;&gt;&lt;br&gt; 头像：&lt;input type=&quot;file&quot; name=&quot;upload&quot;&gt;&lt;br&gt; &lt;input type=&quot;submit&quot; value=&quot;提交&quot;&gt; &lt;/form&gt; ②. 编写文件上传处理代码 @RequestMapping(&quot;/test13&quot;) @ResponseBody public void test13(@RequestParam(value = &quot;username&quot;,required = false) String username,@RequestParam(value = &quot;upload&quot;,required = false) MultipartFile [] uploads) throws IOException { System.out.println(username); for (MultipartFile upload : uploads) { String originalFilename = upload.getOriginalFilename(); upload.transferTo(new File(&quot;F:\\\\upload\\\\&quot; + MyUtils.getUUID() + originalFilename)); } } 15. SpringMVC拦截器 拦截器Interceptor相当于web阶段的过滤器Filter 区别 过滤器 拦截器 使用范围 servlet规范中的一部分，任何Web工程都可以使用 SpringMVC自己的部分，需要使用SpringMVC框架才能使用 拦截范围 在web.xml中配偶之url-pattern映射为/*时，可以拦截所有资源 只拦截控制器方法，对静态资源不进行拦截 ①. 自定义拦截器 创建拦截器类实现 HandlerInterceptor 配置拦截器 代码实现 public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(&quot;被拦截的方法执行之前执行preHandle方法&quot;); //放行请求 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println(&quot;被拦截方法执行之后，视图返回之前执行postHandle方法&quot;); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println(&quot;所有流程执行完毕，执行afterCompletion方法&quot;); } } &lt;mvc:interceptors&gt; &lt;mvc:interceptor&gt; &lt;mvc:mapping path=&quot;/**&quot;/&gt; &lt;bean class=&quot;com.zhiyuan.interceptor.MyInterceptor&quot;/&gt; &lt;/mvc:interceptor&gt; &lt;/mvc:interceptors&gt; ②. 拦截器方法 preHandle()：在请求映射的方法被调用之前，调用该方法，方法返回值为布尔型，用于控制请求是否放行，为 true 放行，为 false 不放行 postHandle()：在请求映射方法执行之后，DispatcherServlet视图渲染之前被调用，可以在方法中操作修改ModelAndView对象 afterCompletion()：在整个请求结束后被调用，也就是在DispatcherServlet渲染视图之后执行 16. SpringMVC异常处理 编译时异常和运行时异常：前者通过捕获异常从而获取异常信息，后者主要通过规范代码开发、测试来减少运行时异常的发生 异常处理思路 通过throws Exception向上抛出，最后由SpringMVC前端控制器交由异常处理器进行处理，如下图： 异常处理两种方式 使用SpringMVC提供的简单异常处理器 SimpleMappingExceptionResolver 实现Spring的异常处理接口 HandlerExceptionResolver 自定义异常处理器 ①. 简单异常处理器 配置简单异常处理器 &lt;!--配置异常处理器--&gt; &lt;bean class=&quot;org.springframework.web.servlet.handler.SimpleMappingExceptionResolver&quot;&gt; &lt;property name=&quot;defaultErrorView&quot; value=&quot;error&quot;/&gt; &lt;property name=&quot;exceptionMappings&quot;&gt; &lt;map&gt; &lt;entry key=&quot;java.lang.ClassCastException&quot; value=&quot;error&quot;/&gt; &lt;entry key=&quot;java.lang.NullPointerException&quot; value=&quot;error&quot;/&gt; &lt;/map&gt; &lt;/property&gt; &lt;/bean&gt; ②. 自定义异常处理器 public class MyException extends Exception{} public class MyExceptionResolver implements HandlerExceptionResolver { public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ModelAndView modelAndView = new ModelAndView(); if(ex instanceof MyException){ modelAndView.addObject(&quot;info&quot;,&quot;自定义异常&quot;); }else if (ex instanceof ClassCastException){ modelAndView.addObject(&quot;info&quot;,&quot;类型转换异常&quot;); }else{ modelAndView.addObject(&quot;info&quot;,&quot;其他异常&quot;); } return modelAndView; } } &lt;!--配置自定义异常处理器--&gt; &lt;bean class=&quot;com.zhiyuan.resolver.MyExceptionResolver&quot;/&gt; 三、案例练习 1. Spring环境搭建 ①. 配置 applicationContext.xml &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd&quot;&gt; &lt;!--1. 配置组件扫描--&gt; &lt;context:component-scan base-package=&quot;com.zhiyuan&quot;/&gt; &lt;!--2. 加载数据库配置文件--&gt; &lt;context:property-placeholder location=&quot;classpath:jdbc.properties&quot;/&gt; &lt;!--3. 配置数据库连接池对象--&gt; &lt;bean id=&quot;dataSource&quot; class=&quot;com.mchange.v2.c3p0.ComboPooledDataSource&quot;&gt; &lt;property name=&quot;driverClass&quot; value=&quot;${c3p0.driverClass}&quot;/&gt; &lt;property name=&quot;jdbcUrl&quot; value=&quot;${c3p0.jdbcUrl}&quot;/&gt; &lt;property name=&quot;user&quot; value=&quot;${c3p0.user}&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;${c3p0.password}&quot;/&gt; &lt;/bean&gt; &lt;!--4. 配置JdbcTemplate对象--&gt; &lt;bean id=&quot;jdbcTemplate&quot; class=&quot;org.springframework.jdbc.core.JdbcTemplate&quot;&gt; &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt; &lt;/bean&gt; &lt;/beans&gt; ②. 配置 spring-mvc.xml &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:mvc=&quot;http://www.springframework.org/schema/mvc&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd&quot;&gt; &lt;!--1. 配置mvc注解驱动--&gt; &lt;mvc:annotation-driven/&gt; &lt;!--2. 交给tomcat去寻找静态资源--&gt; &lt;mvc:default-servlet-handler/&gt; &lt;!--3. 配置内部视图解析器--&gt; &lt;bean class=&quot;org.springframework.web.servlet.view.InternalResourceViewResolver&quot;&gt; &lt;property name=&quot;prefix&quot; value=&quot;/pages/&quot;/&gt; &lt;property name=&quot;suffix&quot; value=&quot;.jsp&quot;/&gt; &lt;/bean&gt; &lt;!--配置文件上传解析器--&gt; &lt;!--此id必须为multipartResolver，是固定写法，不一样就是错--&gt; &lt;bean id=&quot;multipartResolver&quot; class=&quot;org.springframework.web.multipart.commons.CommonsMultipartResolver&quot;&gt; &lt;!--上传文件编码类型--&gt; &lt;property name=&quot;defaultEncoding&quot; value=&quot;UTF-8&quot;/&gt; &lt;!--上传单个文件大小--&gt; &lt;property name=&quot;maxUploadSizePerFile&quot; value=&quot;5242880&quot;/&gt; &lt;!--上传文件总大小--&gt; &lt;property name=&quot;maxUploadSize&quot; value=&quot;5242880&quot;/&gt; &lt;/bean&gt; &lt;/beans&gt; ③. 配置 web.xml &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;web-app xmlns=&quot;http://xmlns.jcp.org/xml/ns/javaee&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd&quot; version=&quot;4.0&quot;&gt; &lt;!--配置全局编码过滤器--&gt; &lt;filter&gt; &lt;filter-name&gt;CharacterEncodingFilter&lt;/filter-name&gt; &lt;filter-class&gt;org.springframework.web.filter.CharacterEncodingFilter&lt;/filter-class&gt; &lt;init-param&gt; &lt;param-name&gt;encoding&lt;/param-name&gt; &lt;param-value&gt;UTF-8&lt;/param-value&gt; &lt;/init-param&gt; &lt;/filter&gt; &lt;filter-mapping&gt; &lt;filter-name&gt;CharacterEncodingFilter&lt;/filter-name&gt; &lt;url-pattern&gt;/*&lt;/url-pattern&gt; &lt;/filter-mapping&gt; &lt;!--配置Spring容器--&gt; &lt;context-param&gt; &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt; &lt;param-value&gt;classpath:applicationContext.xml&lt;/param-value&gt; &lt;/context-param&gt; &lt;listener&gt; &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt; &lt;/listener&gt; &lt;!--配置SpringMVC的前端控制器--&gt; &lt;servlet&gt; &lt;servlet-name&gt;DispatcherServlet&lt;/servlet-name&gt; &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt; &lt;init-param&gt; &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt; &lt;param-value&gt;classpath:spring-mvc.xml&lt;/param-value&gt; &lt;/init-param&gt; &lt;load-on-startup&gt;1&lt;/load-on-startup&gt; &lt;/servlet&gt; &lt;servlet-mapping&gt; &lt;servlet-name&gt;DispatcherServlet&lt;/servlet-name&gt; &lt;url-pattern&gt;/&lt;/url-pattern&gt; &lt;/servlet-mapping&gt; &lt;/web-app&gt; ④. 配置 jdbc.properties c3p0.driverClass=com.mysql.cj.jdbc.Driver c3p0.jdbcUrl=jdbc:mysql://localhost:3306/test?useUnicode=ture&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Hongkong c3p0.user=root c3p0.password=123456 c3p0.initialPoolSize=10 c3p0.maxPoolSize=100 c3p0.checkoutTimeout=3000 ⑤. 配置 pom.xml &lt;dependencies&gt; &lt;dependency&gt; &lt;groupId&gt;mysql&lt;/groupId&gt; &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;version&gt;8.0.21&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;c3p0&lt;/groupId&gt; &lt;artifactId&gt;c3p0&lt;/artifactId&gt; &lt;version&gt;0.9.1.2&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.alibaba&lt;/groupId&gt; &lt;artifactId&gt;druid&lt;/artifactId&gt; &lt;version&gt;1.1.10&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;junit&lt;/groupId&gt; &lt;artifactId&gt;junit&lt;/artifactId&gt; &lt;version&gt;4.12&lt;/version&gt; &lt;scope&gt;test&lt;/scope&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-context&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-test&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-web&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;javax.servlet&lt;/groupId&gt; &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt; &lt;version&gt;3.0.1&lt;/version&gt; &lt;scope&gt;provided&lt;/scope&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;javax.servlet.jsp&lt;/groupId&gt; &lt;artifactId&gt;javax.servlet.jsp-api&lt;/artifactId&gt; &lt;version&gt;2.2.1&lt;/version&gt; &lt;scope&gt;provided&lt;/scope&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt; &lt;artifactId&gt;jackson-core&lt;/artifactId&gt; &lt;version&gt;2.9.0&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt; &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt; &lt;version&gt;2.9.0&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt; &lt;artifactId&gt;jackson-annotations&lt;/artifactId&gt; &lt;version&gt;2.9.0&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;commons-fileupload&lt;/groupId&gt; &lt;artifactId&gt;commons-fileupload&lt;/artifactId&gt; &lt;version&gt;1.3.1&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;commons-io&lt;/groupId&gt; &lt;artifactId&gt;commons-io&lt;/artifactId&gt; &lt;version&gt;2.3&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;commons-logging&lt;/groupId&gt; &lt;artifactId&gt;commons-logging&lt;/artifactId&gt; &lt;version&gt;1.2&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.slf4j&lt;/groupId&gt; &lt;artifactId&gt;slf4j-log4j12&lt;/artifactId&gt; &lt;version&gt;1.7.7&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;log4j&lt;/groupId&gt; &lt;artifactId&gt;log4j&lt;/artifactId&gt; &lt;version&gt;1.2.17&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-tx&lt;/artifactId&gt; &lt;version&gt;5.0.5.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;javax.servlet.jsp.jstl&lt;/groupId&gt; &lt;artifactId&gt;jstl-api&lt;/artifactId&gt; &lt;version&gt;1.2&lt;/version&gt; &lt;/dependency&gt; &lt;/dependencies&gt; 2. 业务代码 四、 MyBatis 1. MyBatis简介 MyBatis是一个优秀的基于Java的持久层框架，它内部封装了JDBC，是开发者只需要关住 SQL语句本身，而不需要花费精力器处理加载驱动、创建Connection连接对象、创建Statement对象等繁杂的过程 MyBatis通过XML和注解的方式将要执行的各种Statement对象配置起来，并通过Java对象和Statement中SQL语句的动态参数进行映射最终生成可执行的SQL语句 最后MyBatis框架执行SQL并将结果映射为Java对象并返回，采用ORM思想解决了实体和数据库的映射问题，对JDBC进行了封装，屏蔽了JDBC的API底层访问细节，使我们不用与JDBC的API打交道就可以完成对数据库的持久化操作 2. MyBatis开发步骤 ①. 添加MyBatis的坐标 ②. 创建user数据表 ③. 编写User实体类 ④. 编写映射文件UserMapper.xml ⑤. 编写核心文件SqlMapConfig.xml ⑥. 编写持久层代码 3. MyBatis查询数据 只有一个参数时建议使用 ${value} ，用在like模糊查询 &lt;!--UserMapper.xml--&gt; &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt; &lt;mapper namespace=&quot;userMapper&quot;&gt; &lt;select id=&quot;findAll&quot; resultType=&quot;com.zhiyuan.mybatis.entity.User&quot;&gt; select * from user &lt;/select&gt; &lt;/mapper&gt; &lt;!--SqlMapConfig.xml--&gt; &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE configuration PUBLIC &quot;-//mybatis.org//DTD Config 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-config.dtd&quot;&gt; &lt;configuration&gt; &lt;environments default=&quot;development&quot;&gt; &lt;environment id=&quot;development&quot;&gt; &lt;transactionManager type=&quot;JDBC&quot;/&gt; &lt;dataSource type=&quot;POOLED&quot;&gt; &lt;property name=&quot;driver&quot; value=&quot;com.mysql.cj.jdbc.Driver&quot;/&gt; &lt;property name=&quot;url&quot; value=&quot;jdbc:mysql://localhost/test?serverTimezone=Asia/Shanghai&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;useSSL=true&quot;/&gt; &lt;property name=&quot;username&quot; value=&quot;root&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;123456&quot;/&gt; &lt;/dataSource&gt; &lt;/environment&gt; &lt;/environments&gt; &lt;mappers&gt; &lt;mapper resource=&quot;com/zhiyuan/mapper/UserMapper.xml&quot;/&gt; &lt;/mappers&gt; &lt;/configuration&gt; @Test public void test1() throws IOException { //读取核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;); //创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //执行sql返回结果 List&lt;User&gt; userList = sqlSession.selectList(&quot;userMapper.findAll&quot;); //输出结果 System.out.println(userList); //释放资源 sqlSession.close(); } 4. MyBatis插入数据 MyBatis默认事务不提交，增删改操作无效，需要使用 sqlSession.commit(); 提交事务 &lt;insert id=&quot;save&quot; parameterType=&quot;com.zhiyuan.mybatis.entity.User&quot;&gt; insert into user values (#{id},#{name},#{age},#{gender}) &lt;/insert&gt; @Test public void test2() throws IOException { //读取核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;); //创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //执行sql返回结果 int insert = sqlSession.insert(&quot;userMapper.save&quot;, new User(8, &quot;zhiyuan008&quot;, 26, &quot;male&quot;)); sqlSession.commit(); System.out.println(&quot;受影响的行数为&quot;+insert); //释放资源 sqlSession.close(); } 5. Mybatis修改数据 &lt;update id=&quot;change&quot; parameterType=&quot;com.zhiyuan.mybatis.entity.User&quot;&gt; update user set name=#{name}, gender=#{gender} where id=#{id} &lt;/update&gt; @Test public void test3() throws IOException { //读取核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;); //创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //执行sql返回结果 int update = sqlSession.update(&quot;userMapper.change&quot;, new User(8, &quot;lisi&quot;, 99, &quot;female&quot;)); sqlSession.commit(); System.out.println(&quot;受影响的行数为&quot;+update); //释放资源 sqlSession.close(); } 6. MyBatis删除数据 &lt;delete id=&quot;delete&quot; parameterType=&quot;java.lang.Integer&quot;&gt; delete from user where id=#{id} &lt;/delete&gt; @Test public void test4() throws IOException { //读取核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;); //创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //执行sql返回结果 int delete = sqlSession.delete(&quot;userMapper.delete&quot;, 8); sqlSession.commit(); System.out.println(&quot;受影响的行数为&quot;+delete); //释放资源 sqlSession.close(); } 7. MyBatis核心配置文件 ①. environments 标签 数据库环境的配置，支持多配置 &lt;!--指定默认的环境名称--&gt; &lt;environments default=&quot;development&quot;&gt; &lt;!--指定当前环境的名称--&gt; &lt;environment id=&quot;development&quot;&gt; &lt;!--指定事务管理类型为JDBC--&gt; &lt;transactionManager type=&quot;JDBC&quot;/&gt; &lt;!--指定当前数据源类型为连接池--&gt; &lt;dataSource type=&quot;POOLED&quot;&gt; &lt;!--数据源配置的基本参数--&gt; &lt;property name=&quot;driver&quot; value=&quot;com.mysql.cj.jdbc.Driver&quot;/&gt; &lt;property name=&quot;url&quot; value=&quot;jdbc:mysql://localhost/test?serverTimezone=Asia/Shanghai&amp;amp;useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;useSSL=true&quot;/&gt; &lt;property name=&quot;username&quot; value=&quot;root&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;123456&quot;/&gt; &lt;/dataSource&gt; &lt;/environment&gt; &lt;/environments&gt; 事务管理器 JDBC MANAGED 数据源 UNPOOLED：每次被请求时打开连接 POOLED：每次请求时从连接池中获取连接 JNDI：应用容器使用 ②. mappers 标签 加载映射 &lt;mappers&gt; &lt;mapper resource=&quot;com/zhiyuan/mapper/UserMapper.xml&quot;/&gt; &lt;/mappers&gt; 使用相对于类路径的资源引用： &lt;mapper resource=&quot;com/zhiyuan/mapper/UserMapper.xml&quot;&gt; 使用完全限定资源定位符：&lt;mapper url=&quot;file:///var/mapper/UserMapper.xml&quot;&gt; 使用映射器接口实现类的完全限定类名：&lt;mapper class=&quot;com.zhiyuan.builder.UserMapper&quot;&gt; 将包内的映射器接口实现全部注册为映射器：&lt;package name=&quot;com.zhiyuan.builder&quot;&gt; ③. properties 标签 加载外部properties配置文件 &lt;properties resource=&quot;jdbc.properties&quot;/&gt; &lt;!--指定默认的环境名称--&gt; &lt;environments default=&quot;development&quot;&gt; &lt;!--指定当前环境的名称--&gt; &lt;environment id=&quot;development&quot;&gt; &lt;!--指定事务管理类型为JDBC--&gt; &lt;transactionManager type=&quot;JDBC&quot;/&gt; &lt;!--指定当前数据源类型为连接池--&gt; &lt;dataSource type=&quot;POOLED&quot;&gt; &lt;!--数据源配置的基本参数--&gt; &lt;property name=&quot;driver&quot; value=&quot;${driverClassName}&quot;/&gt; &lt;property name=&quot;url&quot; value=&quot;${jdbcUrl}&quot;/&gt; &lt;property name=&quot;username&quot; value=&quot;${username}&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;${password}&quot;/&gt; &lt;/dataSource&gt; &lt;/environment&gt; &lt;/environments&gt; ④. typeAliases 标签 为指定类型设置别名，方便映射配置 &lt;typeAliases&gt; &lt;typeAlias type=&quot;com.zhiyuan.mybatis.entity.User&quot; alias=&quot;user&quot;/&gt; &lt;/typeAliases&gt; 必须按照规定的顺序配置typeAliases标签，否则会报错 The content of element type &quot;configuration&quot; must match &quot;(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)&quot;. &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt; &lt;mapper namespace=&quot;userMapper&quot;&gt; &lt;select id=&quot;findAll&quot; resultType=&quot;user&quot;&gt; select * from user &lt;/select&gt; &lt;insert id=&quot;save&quot; parameterType=&quot;user&quot;&gt; insert into user values (#{id},#{name},#{age},#{gender}) &lt;/insert&gt; &lt;update id=&quot;change&quot; parameterType=&quot;user&quot;&gt; update user set name=#{name}, gender=#{gender} where id=#{id} &lt;/update&gt; &lt;delete id=&quot;delete&quot; parameterType=&quot;int&quot;&gt; delete from user where id=#{id} &lt;/delete&gt; &lt;/mapper&gt; 8. MyBatis的API Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;) 读取核心配置文件到IO流 new SqlSessionFactoryBuilder().build(InputStream is) 通过加载核心配置文件创建SqlSessionFactory对象 SqlSessionFactory.openSession(boolean autoCommit) 获取SqlSession对象，给定参数设定是否自动提交事务，不给参数默认不自动提交 SqlSession对象方法 SqlSession.selectOne(String statement,Object parameter) SqlSession.selectList(String statement,Object parameter) SqlSession.insert(String statement,Object parameter) SqlSession.update(String statement,Object parameter) SqlSession.delete(String statement,Object parameter) SqlSession.commit() SqlSession.rollback() 9. MyBatis的代理开发方式 ①. 开发规范 Mapper.xml文件中的namespace应该与Mapper接口的全限定名相同 Mapper接口方法名应该和Mapper.xml中定义的每个statement的id相同 Mapper接口方法的输入参数类型应该和Mapper.xml中定义的每个sql的parameterType相同 Mapper接口方法的输出参数类型应该和Mapper.xml中定义的每个sql的resultType相同 ②. 代码实现 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt; &lt;mapper namespace=&quot;com.zhiyuan.mybatis.dao.UserDao&quot;&gt; &lt;select id=&quot;findAll&quot; resultType=&quot;user&quot;&gt; select * from user &lt;/select&gt; &lt;/mapper&gt; public interface UserDao { public abstract List&lt;User&gt; findAll(); } public class ServiceDemo { public static void main(String[] args) throws IOException { SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;)); SqlSession sqlSession = sqlSessionFactory.openSession(true); UserDao userDao = sqlSession.getMapper(UserDao.class); List&lt;User&gt; userList = userDao.findAll(); System.out.println(userList); } } 10. MyBatis映射文件深入 ①. 动态SQL public interface UserDao { public abstract List&lt;User&gt; findAll(); public abstract List&lt;User&gt; findByIds(List&lt;Integer&gt; list); public abstract List&lt;User&gt; findByCondition(User user); } &lt;select id=&quot;findByIds&quot; parameterType=&quot;list&quot; resultType=&quot;user&quot;&gt; select * from user &lt;where&gt; &lt;foreach collection=&quot;list&quot; open=&quot;id in(&quot; close=&quot;)&quot; item=&quot;id&quot; separator=&quot;,&quot;&gt; #{id} &lt;/foreach&gt; &lt;/where&gt; &lt;/select&gt; &lt;select id=&quot;findByCondition&quot; parameterType=&quot;user&quot; resultType=&quot;user&quot;&gt; select * from user &lt;where&gt; &lt;if test=&quot;id!=0&quot;&gt; and id=#{id} &lt;/if&gt; &lt;if test=&quot;name!=null&quot;&gt; and name=#{name} &lt;/if&gt; &lt;if test=&quot;age!=0&quot;&gt; and age=#{age} &lt;/if&gt; &lt;if test=&quot;gender!=null&quot;&gt; and gender=#{gender} &lt;/if&gt; &lt;/where&gt; &lt;/select&gt; ②. SQL语句的抽取 &lt;sql id=&quot;selectUser&quot;&gt;select * from user&lt;/sql&gt; &lt;select id=&quot;findAll&quot; resultType=&quot;user&quot;&gt; &lt;include refid=&quot;selectUser&quot;/&gt; &lt;/select&gt; ③. 小结 &lt;select&gt;：查询数据 &lt;insert&gt;：插入数据 &lt;update&gt;：修改数据 &lt;delete&gt;：删除数据 &lt;where&gt;：条件 &lt;if&gt;：判断 &lt;foreach&gt;：循环 &lt;sql&gt;：sql抽取 11. MyBatis核心文件深入 ①. typeHandlers 标签 将结果集中的值转换成指定Java类型 BooleanTypeHandler ByteTypeHandler ShortTypeHandler IntegerTypeHandler LongTypeHandler 开发步骤 定义转换类继承自 BaseTypeHandler&lt;T&gt; 实现方法，其中 setNonNullParameter 是java程序设置数据到数据库的回调方法，getNullableResult是查询时mysql字符串类型转为Java的Type类型的方法 在MyBatis核心配置文件中进行注册 测试转换是否正确 public class DateTypeHandler extends BaseTypeHandler&lt;Date&gt; { @Override public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException { long time = parameter.getTime(); ps.setLong(i,time); } @Override public Date getNullableResult(ResultSet rs, String columnName) throws SQLException { return new Date(rs.getLong(columnName)); } @Override public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return new Date(rs.getLong(columnIndex)); } @Override public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return new Date(cs.getLong(columnIndex)); } } &lt;typeHandlers&gt; &lt;typeHandler handler=&quot;com.zhiyuan.mybatis.handler.DateTypeHandler&quot;/&gt; &lt;/typeHandlers&gt; ②. plugins 标签 MyBatis可以使用第三方插件来对功能进行扩展，分页助手PageHelper是将分页的复杂操作进行封装，使用简单的方式即可获得分页的相关数据 开发步骤： 导入通用PageHelper的坐标 在MyBatis核心配置文件中配置PageHelper 测试分页数据获取 导入依赖 &lt;!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --&gt; &lt;dependency&gt; &lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt; &lt;artifactId&gt;pagehelper&lt;/artifactId&gt; &lt;version&gt;5.2.0&lt;/version&gt; &lt;/dependency&gt; &lt;!-- https://mvnrepository.com/artifact/com.github.jsqlparser/jsqlparser --&gt; &lt;dependency&gt; &lt;groupId&gt;com.github.jsqlparser&lt;/groupId&gt; &lt;artifactId&gt;jsqlparser&lt;/artifactId&gt; &lt;version&gt;3.2&lt;/version&gt; &lt;/dependency&gt; 注册分页插件 &lt;!--注册分页插件--&gt; &lt;plugins&gt; &lt;plugin interceptor=&quot;com.github.pagehelper.PageInterceptor&quot;&gt; &lt;!--pagehelper4.0之后不需要配置此项，否则会出错--&gt; &lt;property name=&quot;dialect&quot; value=&quot;mysql&quot;/&gt; &lt;/plugin&gt; &lt;/plugins&gt; 代码实现 @Test public void test5() throws IOException { //读取核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;); //创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); PageHelper.startPage(1,3); List&lt;User&gt; userList = userDao.findAll(); System.out.println(userList); PageInfo&lt;User&gt; userPageInfo = new PageInfo&lt;&gt;(userList); System.out.println(&quot;当前页：&quot; + userPageInfo.getPageNum()); System.out.println(&quot;每页显示条数：&quot; + userPageInfo.getPageSize()); System.out.println(&quot;总条数：&quot; + userPageInfo.getTotal()); System.out.println(&quot;总页数：&quot; + userPageInfo.getPages()); System.out.println(&quot;上一页：&quot; + userPageInfo.getPrePage()); System.out.println(&quot;下一页：&quot; + userPageInfo.getNextPage()); System.out.println(&quot;是否是首页：&quot; + userPageInfo.isIsFirstPage()); System.out.println(&quot;是否是尾页：&quot; + userPageInfo.isIsLastPage()); sqlSession.close(); } 12. MyBatis的多表操作 ①. 一对一查询模型 根据指定订单号查询订单和用户信息 创建订单Order对象 package com.zhiyuan.mybatis.entity; import java.util.Date; public class Order { private int id; private String ordertime; private double total; private User user; public Order() {} public Order(int id, String ordertime, double total, User user) { this.id = id; this.ordertime = ordertime; this.total = total; this.user = user; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getOrdertime() { return ordertime; } public void setOrdertime(String ordertime) { this.ordertime = ordertime; } public double getTotal() { return total; } public void setTotal(double total) { this.total = total; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { final StringBuffer sb = new StringBuffer(&quot;Order{&quot;); sb.append(&quot;id=&quot;).append(id); sb.append(&quot;, ordertime=&quot;).append(ordertime); sb.append(&quot;, total=&quot;).append(total); sb.append(&quot;, user=&quot;).append(user); sb.append('}'); return sb.toString(); } } 配置映射文件 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt; &lt;mapper namespace=&quot;com.zhiyuan.mybatis.dao.OrderDao&quot;&gt; &lt;resultMap id=&quot;orderMap&quot; type=&quot;order&quot;&gt; &lt;id column=&quot;oid&quot; property=&quot;id&quot;/&gt; &lt;result column=&quot;ordertime&quot; property=&quot;ordertime&quot;/&gt; &lt;result column=&quot;total&quot; property=&quot;total&quot;/&gt; &lt;result column=&quot;uid&quot; property=&quot;user.id&quot;/&gt; &lt;result column=&quot;name&quot; property=&quot;user.name&quot;/&gt; &lt;result column=&quot;age&quot; property=&quot;user.age&quot;/&gt; &lt;result column=&quot;gender&quot; property=&quot;user.gender&quot;/&gt; &lt;/resultMap&gt; &lt;select id=&quot;findAll&quot; resultMap=&quot;orderMap&quot;&gt; select o.id oid,u.id uid,ordertime,total,name,age,gender from orders o,user u where o.uid=u.id &lt;/select&gt; &lt;/mapper&gt; 优化配置文件 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt; &lt;mapper namespace=&quot;com.zhiyuan.mybatis.dao.OrderDao&quot;&gt; &lt;resultMap id=&quot;orderMap&quot; type=&quot;order&quot;&gt; &lt;id column=&quot;oid&quot; property=&quot;id&quot;/&gt; &lt;result column=&quot;ordertime&quot; property=&quot;ordertime&quot;/&gt; &lt;result column=&quot;total&quot; property=&quot;total&quot;/&gt; &lt;association property=&quot;user&quot; javaType=&quot;user&quot;&gt; &lt;id column=&quot;uid&quot; property=&quot;id&quot;/&gt; &lt;result column=&quot;name&quot; property=&quot;name&quot;/&gt; &lt;result column=&quot;age&quot; property=&quot;age&quot;/&gt; &lt;result column=&quot;gender&quot; property=&quot;gender&quot;/&gt; &lt;/association&gt; &lt;/resultMap&gt; &lt;select id=&quot;findAll&quot; resultMap=&quot;orderMap&quot;&gt; select o.id oid,u.id uid,ordertime,total,name,age,gender from orders o,user u where o.uid=u.id &lt;/select&gt; &lt;/mapper&gt; ②. 一对多查询模型 根据用户id获取用户所有订单信息 创建用户User对象 package com.zhiyuan.mybatis.entity; import java.util.List; public class User { private int id; private String name; private int age; private String gender; private List&lt;Order&gt; orderList; public User() {} public User(int id, String name, int age, String gender) { this.id = id; this.name = name; this.age = age; this.gender = gender; } public List&lt;Order&gt; getOrderList() { return orderList; } public void setOrderList(List&lt;Order&gt; orderList) { this.orderList = orderList; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { final StringBuffer sb = new StringBuffer(&quot;User{&quot;); sb.append(&quot;id=&quot;).append(id); sb.append(&quot;, name='&quot;).append(name).append('\\''); sb.append(&quot;, age=&quot;).append(age); sb.append(&quot;, gender='&quot;).append(gender).append('\\''); sb.append(&quot;, orderList=&quot;).append(orderList); sb.append('}'); return sb.toString(); } } 配置UserMapper.xml &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt; &lt;mapper namespace=&quot;com.zhiyuan.mybatis.dao.UserDao&quot;&gt; &lt;sql id=&quot;selectUser&quot;&gt;select * from user&lt;/sql&gt; &lt;resultMap id=&quot;userMap&quot; type=&quot;user&quot;&gt; &lt;id column=&quot;uid&quot; property=&quot;id&quot;/&gt; &lt;result column=&quot;name&quot; property=&quot;name&quot;/&gt; &lt;result column=&quot;age&quot; property=&quot;age&quot;/&gt; &lt;result column=&quot;gender&quot; property=&quot;gender&quot;/&gt; &lt;collection property=&quot;orderList&quot; ofType=&quot;order&quot;&gt; &lt;id column=&quot;oid&quot; property=&quot;id&quot;/&gt; &lt;result column=&quot;ordertime&quot; property=&quot;ordertime&quot;/&gt; &lt;result column=&quot;total&quot; property=&quot;total&quot;/&gt; &lt;/collection&gt; &lt;/resultMap&gt; &lt;select id=&quot;findAllOrder&quot; resultMap=&quot;userMap&quot;&gt; select o.id oid,u.id uid,ordertime,total,name,age,gender from orders o,user u where o.uid=u.id &lt;/select&gt; &lt;/mapper&gt; 测试结果 @Test public void test7() throws IOException { //读取核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;); //创建会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); List&lt;Order&gt; allOrder = userDao.findAllOrder(); System.out.println(allOrder); } ③. 多对多查询模型 查询一个用户对应的多个角色 ④. 小结 MyBatis多表配置方式 一对一配置：使用 &lt;resultMap&gt; 做配置 一对多配置：使用 &lt;resultMap&gt;+&lt;collection&gt; 做配置 多对多配置：使用 &lt;resultMap&gt;+&lt;collection&gt; 做配置 13. MyBatis的注解开发 ①. @Insert 实现插入 ②. @Update 实现更新 ③. @Delete 实现删除 ④. @Select 实现查询 ⑤. @Result 实现结果集封装 ⑥. @Results 可以与 @Result 一起使用，封装多个结果集 ⑦. @One 实现一对一结果集封装 ⑧. @Many 实现一对多结果集封装 14. 导出资源失败问题 &lt;build&gt; &lt;resources&gt; &lt;resource&gt; &lt;directory&gt;src/main/resources&lt;/directory&gt; &lt;includes&gt; &lt;include&gt;**/*.properties&lt;/include&gt; &lt;include&gt;**/*.xml&lt;/include&gt; &lt;/includes&gt; &lt;filtering&gt;false&lt;/filtering&gt; &lt;/resource&gt; &lt;resource&gt; &lt;directory&gt;src/main/java&lt;/directory&gt; &lt;includes&gt; &lt;include&gt;**/*.properties&lt;/include&gt; &lt;include&gt;**/*.xml&lt;/include&gt; &lt;/includes&gt; &lt;filtering&gt;false&lt;/filtering&gt; &lt;/resource&gt; &lt;/resources&gt; &lt;/build&gt; 15. 数据库和实体类映射业内共识 表名小写：User.java --- user EduUser --- edu_user 《阿里Java开发规范》中的约定 不得使用大写字母，不得连写两个单词； 表必备三字段：id, gmt_create, gmt_modified； 其中 id 必为主键，类型为 unsigned bigint、单表时自增、步长为 1；分表时改为从TDDL Sequence 取值，确保分表之间的全局唯一。gmt_create, gmt_modified 的类型均为date_time 类型； 用尽量少的存储空间来存 数一个字段的数据； 能用int的就不用char或者varchar； 能用tinyint的就不用int； 能用varchar(20)的就不用varchar(255)； 时间戳字段尽量用int型，如gmt_create:表示从 ‘1970-01-01 08:00:00’ 开始的int秒数，采用英文单词的过去式；gmtCreated:表示datetime类型的时间，即形如 '1980-01-01 00:00:00’的时间串，Java中对应的类型为Timestamp 不得使用外键与级联，一切外键概念必须在应用层解决。 说明：（概念解释）学生表中的 student_id 是主键，那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id，同时触发成绩表中的 student_id 更新，则为级联更新。外键与级联更新适用于单机低并发，不适合分布式、高并发集群；级联更新是强阻塞，存在数据库更新风暴的风险；外键影响数据库的插入速度。 禁止使用存储过程，存储过程难以调试和扩展，更没有移植性。 16. MyBatis的逆向创建POJO &lt;!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core --&gt; &lt;dependency&gt; &lt;groupId&gt;org.mybatis.generator&lt;/groupId&gt; &lt;artifactId&gt;mybatis-generator-core&lt;/artifactId&gt; &lt;version&gt;1.3.7&lt;/version&gt; &lt;/dependency&gt; 几乎没人用了 五、SSM框架整合 1. 原始整合方式 ①. 导入pom.xml依赖 &lt;dependencies&gt; &lt;dependency&gt; &lt;groupId&gt;junit&lt;/groupId&gt; &lt;artifactId&gt;junit&lt;/artifactId&gt; &lt;version&gt;4.11&lt;/version&gt; &lt;scope&gt;test&lt;/scope&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-context&lt;/artifactId&gt; &lt;version&gt;5.2.1.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt; &lt;version&gt;5.2.1.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.mybatis&lt;/groupId&gt; &lt;artifactId&gt;mybatis&lt;/artifactId&gt; &lt;version&gt;3.4.6&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.mybatis&lt;/groupId&gt; &lt;artifactId&gt;mybatis-spring&lt;/artifactId&gt; &lt;version&gt;2.0.5&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.aspectj&lt;/groupId&gt; &lt;artifactId&gt;aspectjweaver&lt;/artifactId&gt; &lt;version&gt;1.9.6&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.mchange&lt;/groupId&gt; &lt;artifactId&gt;c3p0&lt;/artifactId&gt; &lt;version&gt;0.9.5.2&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-tx&lt;/artifactId&gt; &lt;version&gt;5.2.1.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-test&lt;/artifactId&gt; &lt;version&gt;5.2.1.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt; &lt;version&gt;5.2.1.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;javax.servlet&lt;/groupId&gt; &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt; &lt;version&gt;4.0.1&lt;/version&gt; &lt;scope&gt;provided&lt;/scope&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;javax.servlet.jsp&lt;/groupId&gt; &lt;artifactId&gt;javax.servlet.jsp-api&lt;/artifactId&gt; &lt;version&gt;2.3.3&lt;/version&gt; &lt;scope&gt;provided&lt;/scope&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;mysql&lt;/groupId&gt; &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;version&gt;8.0.21&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;javax.servlet.jsp.jstl&lt;/groupId&gt; &lt;artifactId&gt;javax.servlet.jsp.jstl-api&lt;/artifactId&gt; &lt;version&gt;1.2.2&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;taglibs&lt;/groupId&gt; &lt;artifactId&gt;standard&lt;/artifactId&gt; &lt;version&gt;1.1.2&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;log4j&lt;/groupId&gt; &lt;artifactId&gt;log4j&lt;/artifactId&gt; &lt;version&gt;1.2.17&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;commons-logging&lt;/groupId&gt; &lt;artifactId&gt;commons-logging&lt;/artifactId&gt; &lt;version&gt;1.2&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;commons-io&lt;/groupId&gt; &lt;artifactId&gt;commons-io&lt;/artifactId&gt; &lt;version&gt;2.6&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;commons-fileupload&lt;/groupId&gt; &lt;artifactId&gt;commons-fileupload&lt;/artifactId&gt; &lt;version&gt;1.3.1&lt;/version&gt; &lt;/dependency&gt; &lt;/dependencies&gt; ②. 创建实体类Account.java ③. 编写AccountMapper.java public interface AccountMapper { public abstract void add(Account account); public abstract List&lt;Account&gt; findAll(); } ④. 编写AccountService.java public interface AccountService { public abstract void add(Account account); public abstract List&lt;Account&gt; findAll(); } ⑤. 编写AccountServiceImpl.java @Service(&quot;accountService&quot;) public class AccountServiceImpl implements AccountService { private static AccountMapper accountMapper = MyUtils.getSqlSession().getMapper(AccountMapper.class); @Override public void add(Account account) { accountMapper.add(account); } @Override public List&lt;Account&gt; findAll() { return accountMapper.findAll(); } } ⑥. 编写工具类MyUtils.java public class MyUtils { private static SqlSession sqlSession; private MyUtils(){} static{ InputStream resourceAsStream = null; try { resourceAsStream = Resources.getResourceAsStream(&quot;SqlMapConfig.xml&quot;); sqlSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession(true); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSession; } } ⑦. 编写AccountController.java @RestController @RequestMapping(&quot;/account&quot;) public class AccountController { @Autowired private AccountService accountService; @PostMapping(value = &quot;/add&quot;,produces = &quot;text/html;charset=utf-8&quot;) @ResponseBody public String add(Account account){ accountService.add(account); return &quot;保存成功&quot;; } @RequestMapping(&quot;/findAll&quot;) public ModelAndView findAll(){ List&lt;Account&gt; accountList = accountService.findAll(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject(&quot;accountList&quot;,accountList); modelAndView.setViewName(&quot;accountlist&quot;); return modelAndView; } } ⑧. 配置Spring+SpringMVC+MyBatis Spring：applicationContext.xml &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd &quot;&gt; &lt;!--配置组件扫描--&gt; &lt;context:component-scan base-package=&quot;com.zhiyuan.ssm&quot;&gt; &lt;!--取消对Controller注解的扫描，交由SpringMVC来管理--&gt; &lt;context:exclude-filter type=&quot;annotation&quot; expression=&quot;org.springframework.stereotype.Controller&quot;/&gt; &lt;/context:component-scan&gt; &lt;/beans&gt; SpringMVC：spring-mvc.xml &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:context=&quot;http://www.springframework.org/schema/context&quot; xmlns:mvc=&quot;http://www.springframework.org/schema/mvc&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd &quot;&gt; &lt;!--配置组件扫描，主要扫描Controller--&gt; &lt;context:component-scan base-package=&quot;com.zhiyuan.ssm.controller&quot;/&gt; &lt;!--配置mvc注解驱动--&gt; &lt;mvc:annotation-driven/&gt; &lt;!--交由tomcat寻找静态资源--&gt; &lt;mvc:default-servlet-handler/&gt; &lt;!--配置内部资源视图解析器--&gt; &lt;bean id=&quot;resourceViewResolver&quot; class=&quot;org.springframework.web.servlet.view.InternalResourceViewResolver&quot;&gt; &lt;property name=&quot;prefix&quot; value=&quot;/pages/&quot;/&gt; &lt;property name=&quot;suffix&quot; value=&quot;.jsp&quot;/&gt; &lt;/bean&gt; &lt;/beans&gt; MyBatis：AccountMapper.xml + SqlMapConfig.xml &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt; &lt;mapper namespace=&quot;com.zhiyuan.ssm.mapper.AccountMapper&quot;&gt; &lt;insert id=&quot;add&quot; parameterType=&quot;account&quot;&gt; insert into account (name,balance) values (#{name},#{balance}) &lt;/insert&gt; &lt;select id=&quot;findAll&quot; resultType=&quot;account&quot;&gt; select * from account &lt;/select&gt; &lt;/mapper&gt; &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE configuration PUBLIC &quot;-//mybatis.org//DTD Config 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-config.dtd&quot;&gt; &lt;configuration&gt; &lt;!--引入数据库配置文件--&gt; &lt;properties resource=&quot;jdbc.properties&quot;/&gt; &lt;!--定义别名--&gt; &lt;typeAliases&gt; &lt;!--&lt;typeAlias type=&quot;com.zhiyuan.ssm.entity.Account&quot; alias=&quot;account&quot;/&gt;--&gt; &lt;package name=&quot;com.zhiyuan.ssm.entity&quot;/&gt; &lt;/typeAliases&gt; &lt;!--配置环境--&gt; &lt;environments default=&quot;developement&quot;&gt; &lt;environment id=&quot;developement&quot;&gt; &lt;transactionManager type=&quot;JDBC&quot;/&gt; &lt;dataSource type=&quot;POOLED&quot;&gt; &lt;property name=&quot;driver&quot; value=&quot;${driverClassName}&quot;/&gt; &lt;property name=&quot;url&quot; value=&quot;${jdbcUrl}&quot;/&gt; &lt;property name=&quot;username&quot; value=&quot;${dbusername}&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;${dbpassword}&quot;/&gt; &lt;/dataSource&gt; &lt;/environment&gt; &lt;/environments&gt; &lt;!--加载映射--&gt; &lt;mappers&gt; &lt;mapper resource=&quot;com\\zhiyuan\\ssm\\mapper\\AccountMapper.xml&quot;/&gt; &lt;/mappers&gt; &lt;/configuration&gt; 2. 优化整合方式 ①. 导入pom.xml依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework&lt;/groupId&gt; &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt; &lt;version&gt;5.2.1.RELEASE&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.mchange&lt;/groupId&gt; &lt;artifactId&gt;c3p0&lt;/artifactId&gt; &lt;version&gt;0.9.5.2&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.mybatis&lt;/groupId&gt; &lt;artifactId&gt;mybatis-spring&lt;/artifactId&gt; &lt;version&gt;2.0.5&lt;/version&gt; &lt;/dependency&gt; ②. 将SqlSessionFactory配置到Spring容器中 &lt;!--加载配置文件--&gt; &lt;context:property-placeholder location=&quot;classpath:jdbc.properties&quot;/&gt; &lt;!--配置数据库连接池信息--&gt; &lt;bean id=&quot;dataSource&quot; class=&quot;com.mchange.v2.c3p0.ComboPooledDataSource&quot;&gt; &lt;property name=&quot;driverClass&quot; value=&quot;${driverClassName}&quot;/&gt; &lt;property name=&quot;jdbcUrl&quot; value=&quot;${jdbcUrl}&quot;/&gt; &lt;property name=&quot;user&quot; value=&quot;${dbusername}&quot;/&gt; &lt;property name=&quot;password&quot; value=&quot;${dbpassword}&quot;/&gt; &lt;/bean&gt; &lt;!--配置SqlSessionFactory--&gt; &lt;bean id=&quot;sqlSessionFactory&quot; class=&quot;org.mybatis.spring.SqlSessionFactoryBean&quot;&gt; &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt; &lt;!--引入其他配置信息--&gt; &lt;property name=&quot;configLocation&quot; value=&quot;classpath:SqlMapConfig-Spring.xml&quot;/&gt; &lt;/bean&gt; &lt;!--扫描所有mapper创建对象--&gt; &lt;bean class=&quot;org.mybatis.spring.mapper.MapperScannerConfigurer&quot;&gt; &lt;property name=&quot;basePackage&quot; value=&quot;com.zhiyuan.ssm.mapper&quot;/&gt; &lt;/bean&gt; &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;!DOCTYPE configuration PUBLIC &quot;-//mybatis.org//DTD Config 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-config.dtd&quot;&gt; &lt;configuration&gt; &lt;!--引入数据库配置文件--&gt; &lt;properties resource=&quot;jdbc.properties&quot;/&gt; &lt;!--定义别名--&gt; &lt;typeAliases&gt; &lt;!--&lt;typeAlias type=&quot;com.zhiyuan.ssm.entity.Account&quot; alias=&quot;account&quot;/&gt;--&gt; &lt;package name=&quot;com.zhiyuan.ssm.entity&quot;/&gt; &lt;/typeAliases&gt; &lt;!--加载映射--&gt; &lt;mappers&gt; &lt;mapper resource=&quot;com\\zhiyuan\\ssm\\mapper\\AccountMapper.xml&quot;/&gt; &lt;/mappers&gt; &lt;/configuration&gt; ③. 书写AccountController.java @Service(&quot;accountService&quot;) public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; @Override public void add(Account account) { accountMapper.add(account); } @Override public List&lt;Account&gt; findAll() { return accountMapper.findAll(); } } ④. 声明式事务控制 &lt;!--配置平台事务管理对象--&gt; &lt;bean id=&quot;transactionManager&quot; class=&quot;org.springframework.jdbc.datasource.DataSourceTransactionManager&quot;&gt; &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt; &lt;/bean&gt; &lt;!--配置事务增强--&gt; &lt;tx:advice id=&quot;txAdvice&quot;&gt; &lt;tx:attributes&gt; &lt;tx:method name=&quot;*&quot;/&gt; &lt;/tx:attributes&gt; &lt;/tx:advice&gt; &lt;!--事务的织入--&gt; &lt;aop:config&gt; &lt;aop:advisor advice-ref=&quot;txAdvice&quot; pointcut=&quot;execution(* com.zhiyuan.ssm.service.impl.*.*(..))&quot;/&gt; &lt;/aop:config&gt; 六、分布式框架 软件架构的发展过程：单体架构 --- 垂直架构 --- SOA架构 --- 微服务架构 单体架构 特点：全部功能几种在一个项目内（All in one） 优点：架构简单，前期开发成本低，开发周期短，适合小型项目 缺点： 全部功能集成在一个工程中，对于代行项目不宜开发、扩展和维护 技术栈受限，只能用一种语言开发 系统性能扩展只能通过扩展集群节点，成本高 垂直架构 特点：按照业务进行分割，形成小的单体项目 优点：技术栈可扩展 缺点： 功能集中在一个项目中，不利于开发、扩展和维护 系统扩张只能通过集群方式 项目之间功能冗杂、数据冗余、耦合性强 SOA架构 特点：将重复功能或模块抽取成组件的形式，对外提供服务，在项目与服务之间使用ESB（企业服务总线）的形式作为通信的桥梁 优点： 重复功能或模块抽取为服务，提高开发效率 可重用性高，可维护性高 缺点： 各系统之间有义务不同，很难确认功能或模块是重复的 抽取服务的粒度大 系统和服务之间耦合度高 微服务架构 特点：将系统服务层完全独立出来，抽取为一个个的微服务，抽取的粒度更细，遵循单一原则，采用轻量级框架协议传输 优点： 服务拆分粒度更细，有利于提高开发效率 可以针对不同服务指定对应的优化方案 适用于互联网时代，产品迭代周期更短 缺点： 粒度太大导致服务太多，维护成本高 分布式系统开发的技术成本高，对团队的挑战大 1. Apache Dubbo Apache Dubbo是一款高性能的Java RPC框架，其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC框架，可以和Spring框架无缝集成。 RPC：remote procedure call（远程过程调用） 官网：https://dubbo.apache.org/zh/ Apache Dubbo提供了三大核心能力： 面向接口的远程方法调用 智能容错和负载均衡 服务自动注册和发现 七、Vue学习 1. 简介 Vue 读音与View类似，是一套用于构建用户界面的 渐进式框架 ，与其他框架不同的是，Vue被设计为可以自定向上逐层应用，Vue的和辛苦只关注视图层，不仅易上手，还便于与第三方库或既有项目整合，另一方面，当与现代化的工具链以及各种支持类库结合使用时，Vue也完全能够为复杂的单页应用提供驱动 渐进式：可以选择性的使用该框架中的一个或一些组件，这些组件的使用也不需要将框架全部组件都应用，而且用了这些组件也不要求你的系统全部都使用该框架 前端主流框架 Vue.js React.js AngularJS 官网：https://cn.vuejs.org/ 2. 搭建示例工程 Vue的使用方式 可以引用在线的Vue.js 可以离线下载Vue.js npm包资源管理器，可以下载Vue.js 步骤 npm init -y：初始化 npm install vue --save ：局部安装vue模块 3. 演示双向绑定与事件处理 ①. 插值引用 {{key}} &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;第一课&lt;/title&gt; &lt;script src=&quot;node_modules/vue/dist/vue.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div id=&quot;app&quot;&gt; &lt;h2&gt;{{name}}学Vue&lt;/h2&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; var app = new Vue({ el:&quot;#app&quot;, data:{ name:&quot;絷缘&quot; } }); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; ②. 双向绑定 v-model &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;第一课&lt;/title&gt; &lt;script src=&quot;node_modules/vue/dist/vue.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div id=&quot;app&quot;&gt; &lt;input type=&quot;text&quot; v-model=&quot;day&quot;/&gt; &lt;h2&gt;{{name}}学Vue，学了{{day}}天&lt;/h2&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; var app = new Vue({ el:&quot;#app&quot;, data:{ name:&quot;絷缘&quot;, day:20 } }); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; ③. 事件处理 v-on:事件 &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;第一课&lt;/title&gt; &lt;script src=&quot;node_modules/vue/dist/vue.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div id=&quot;app&quot;&gt; &lt;input type=&quot;text&quot; v-model=&quot;day&quot;/&gt; &lt;button v-on:click=&quot;day++&quot;&gt;点我加一&lt;/button&gt; &lt;h2&gt;{{name}}学Vue，学了{{day}}天&lt;/h2&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; var app = new Vue({ el:&quot;#app&quot;, data:{ name:&quot;絷缘&quot;, day:20 } }); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; 4. Vue的生命周期以及钩子函数 ①. 生命周期 创建实例：每个Vue应用都是通过Vue函数创建一个新的Vue实例开始的 var app = new Vue({ //在构造函数中传入一个对象，并在对象中声明各种Vue所需数据和方法 }); 装载模板：每个Vue实例都需要关联一段html，Vue会基于当前模板进行视图渲染，关联模板通过 el 实现 var app = new Vue({ //这样Vue就与id为app的元素关联上了，Vue就可以基于id为app的元素进行渲染数据了 el:&quot;#app&quot; }); 渲染模板：当Vue实例被创建时，它会尝试获取在data中定义的所有属性，用于视图的数据渲染，并且监视data中属性的变化，当data发生变化，所有与之绑定的视图都将重新渲染，这就是响应式系统 ②. 钩子函数 Vue为生命周期中的每个状态都设置了钩子函数（监听函数），每当Vue实例处于不同的生命周期时，对应的函数就会被触发调用 beforeCreate()：Vue实例创建之前执行代码 created()：Vue实例被创建后执行代码，常用于初始化数据 beforeMount mounted updated beforeUpdate destroyed beforeDestroy 钩子函数不要使用箭头函数的方式编写 因为箭头函数并没有 this，this 会作为变量一直向上级词法作用域查找，直至找到为止，经常导致 Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。 5. 插值 {{}} 指令：是指带有v- 前缀的特殊属性，例如 v-model 插值表达式：{{key}} 用于替换插值表达式：v-text v-html v-text：将数据输出到元素内部，如果输出的数据有HTML代码，作为普通文本输出 v-html：将数据输出到元素的内部，如果输出的数据有HTML代码，会被渲染到页面上 6. 双向绑定 v-model 因为是双向绑定，意味着数据的改变会影响到视图的渲染，而视图的改变同样会影响到数据的改变，这就要求视图中的元素具备可修改数据的功能，所以可以双向绑定的元素是固定有限的 input select textarea checkbox radio components 7. 事件绑定 v-on 格式：v-on:click=&quot;num++&quot; 简写为：@click=&quot;num++&quot; 实例代码： &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;第一课&lt;/title&gt; &lt;script src=&quot;node_modules/vue/dist/vue.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div id=&quot;app&quot;&gt; &lt;input type=&quot;text&quot; v-model=&quot;day&quot;/&gt; &lt;button v-on:click=&quot;day++&quot;&gt;点我加一&lt;/button&gt; &lt;h2&gt;{{name}}学Vue，学了{{day}}天&lt;/h2&gt; &lt;button @click=&quot;decrement&quot;&gt;点我减一{{num}}&lt;/button&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; var app = new Vue({ el:&quot;#app&quot;, data:{ name:&quot;絷缘&quot;, day:20, num:0 }, created() { this.num = 100; }, methods:{ decrement(){ this.num--; } } }); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; 事件修饰符 .stop：阻止事件冒泡 .prevent：阻止默认事件发生 .capture：使用事件捕获模式 .self：只有元素自身触发事件才执行（冒泡或捕获的都不执行） .once：只执行一次 事件冒泡测试 &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;事件冒泡测试&lt;/title&gt; &lt;script src=&quot;./node_modules/vue/dist/vue.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div id=&quot;app&quot;&gt; &lt;div style=&quot;width: 100px;height: 100px;background-color: #666666;&quot; @click=&quot;print('div被点击')&quot;&gt; &lt;button @click.stop=&quot;print('按钮被点击')&quot;&gt;事件冒泡&lt;/button&gt; &lt;a href=&quot;https://www.baidu.com&quot; target=&quot;_blank&quot; @click.prevent=&quot;print('a被点击')&quot;&gt;前往百度&lt;/a&gt; &lt;/div&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; var app = new Vue({ el:&quot;#app&quot;, data:{ name:&quot;絷缘&quot; }, methods:{ print(str){ console.log(str); } } }); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; 8. 数组、对象遍历 指令：v-for 实例代码： &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;集合数据遍历渲染&lt;/title&gt; &lt;script src=&quot;./node_modules/vue/dist/vue.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div id=&quot;app&quot;&gt; &lt;table width=&quot;500&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot; border=&quot;1&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;序号&lt;/th&gt; &lt;th&gt;userId&lt;/th&gt; &lt;th&gt;userName&lt;/th&gt; &lt;th&gt;userAge&lt;/th&gt; &lt;th&gt;userGender&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr v-for=&quot;(user,index) in users&quot;&gt; &lt;th&gt;{{index}}&lt;/th&gt; &lt;th&gt;{{user.userId}}&lt;/th&gt; &lt;th&gt;{{user.userName}}&lt;/th&gt; &lt;th&gt;{{user.userAge}}&lt;/th&gt; &lt;th&gt;{{user.userGender}}&lt;/th&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;/div&gt; &lt;script type=&quot;text/javascript&quot;&gt; var app = new Vue({ el:&quot;#app&quot;, data:{ users:[ {&quot;userId&quot;:1,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:2,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:3,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:4,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:5,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:6,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:7,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:8,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:9,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, {&quot;userId&quot;:10,&quot;userName&quot;:&quot;絷缘&quot;,&quot;userAge&quot;:23,&quot;userGender&quot;:&quot;male&quot;}, ] } }); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; 为消除数据被增删时对视图渲染产生的影响，我们需要使用一个特殊的语法 :key=&quot;&quot;，通过key与集合元素中单个元素的唯一属性进行绑定，保证数据的唯一性，从 而使数据增删时，视图渲染不受影响 :key=&quot;&quot; 是 v-bind:key=&quot;&quot; 的简写 8. 指令 v-if v-show ①. v-if v-else-if v-else DOM动态渲染，对元素进行动态添加或删除 ②. v-show 通过元素的css display:none 对元素做显示或隐藏 9. 指令 v-bind 八、SpringBoot 1. 什么是SpringBoot ①. 概述 SpringBoot称为搭建程序的脚手架，或者说是便捷搭建基于Spring工程的脚手架 ②. 作用 帮助开发人员快速地构建庞大的Spring项目，并且尽可能地减少一切xml文件的配置，做到开箱即用，让开发人员将注意力放在业务上而不是配置上 ③. 解决的问题 繁杂的配置文件 混乱的依赖管理 ④. 主要特点 创建独立的Spring应用，为所有Spring开发者提供一个非常快速地、广泛接受的入门体验 直接嵌入应用服务器，如tomcat、jetty、undertow等，不需要去部署war包 提供固定的启动器依赖去简化组件配置，实现开箱即用 自动地配置Spring和其他有需要的第三方依赖 提供了一些代行项目中常见的非功能性特性，如内嵌服务器、安全、指标、健康检测、外部配置等 绝对没有代码生成，也无需xml配置 2. SpringBoot配置文件 ①. 配置文件类型 properties yml / yaml ②. yaml：以数据为核心的配置文件 基本语法 大小写敏感 数据值前必须由空格分隔 使用空格缩进表示层级关系，相同缩进表示同一级 数据格式 对象 server: port: 8080 server: {port: 8080} 数组 language: - zh-cn - en - jp language: [zh-cn,en,jp] 纯量 username: 'zhiyuan\\n' #单引号内原样输出 username: &quot;zhiyuan\\n&quot; #双引号内可以识别转义字符 参数引用 通过$符号可以引用已经存在于yaml配置文件中的纯量参数 url: &quot;jdbc:mysql://localhost:3306/test?serverTimeZone=HongKong&quot; jdbc: url: ${url} ③. SpringBoot内部配置加载顺序 file:./config/：从当前项目路径中的config目录中加载配置文件 file:./：从当前项目的根目录中加载配置文件 classpath:/config/：classpath的config目录中加载配置文件 classpath:/：classpath的根目录中加载配置文件 3. SpringBoot入门 ①. 步骤 创建maven项目 添加SpringBoot父级依赖 &lt;parent&gt; &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;version&gt;2.4.2&lt;/version&gt; &lt;/parent&gt; 添加SpringBoot启动器依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt; &lt;/dependency&gt; 设置JDK版本信息 &lt;properties&gt; &lt;java.version&gt;1.8&lt;/java.version&gt; &lt;/properties&gt; 编写启动引导类 /** * Spring Boot工程都有一个启动引导类，这是工程的入口，并在引导类上添加 @SpringBootApplication 注解 */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } 编写简单Controller @RestController public class TestController { @GetMapping(&quot;/hello&quot;) public String hello(){ return &quot;Hello!SpringBoot!&quot;; } } 成功访问 ②. Java代码方式配置 Java配置主要靠Java类和一些注解，比较常用的注解有 @Configuration：声明一个类作为配置类，代替xml文件 @Bean：声明在方法上，将方法返回值加入Bean容器，代替bean标签 @Value：属性注入 @PropertySource：指定外部属性文件 实现配置数据库连接池 导入依赖 &lt;dependency&gt; &lt;groupId&gt;com.alibaba&lt;/groupId&gt; &lt;artifactId&gt;druid&lt;/artifactId&gt; &lt;version&gt;1.2.4&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;mysql&lt;/groupId&gt; &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;version&gt;8.0.21&lt;/version&gt; &lt;/dependency&gt; 创建配置文件 jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/springbootdb?serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456 创建配置类 @Configuration @PropertySource(&quot;classpath:jdbc.properties&quot;) public class JdbcConfig { @Value(&quot;${jdbc.driverClassName}&quot;) String driverClassName; @Value(&quot;${jdbc.url}&quot;) String url; @Value(&quot;${jdbc.username}&quot;) String username; @Value(&quot;${jdbc.password}&quot;) String password; @Bean public DataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(this.driverClassName); druidDataSource.setUrl(this.url); druidDataSource.setUsername(this.username); druidDataSource.setPassword(this.password); return druidDataSource; } } 根据类型注入属性 @RestController public class TestController { @Autowired private DataSource ds; @GetMapping(&quot;/hello&quot;) public String hello(){ System.out.println(ds); return &quot;Hello!SpringBoot!&quot;; } } 控制台打印 { CreateTime:&quot;2021-03-13 15:02:01&quot;, ActiveCount:0, PoolingCount:0, CreateCount:0, DestroyCount:0, CloseCount:0, ConnectCount:0, Connections:[ ] } ③. SpringBoot属性注入方式 @ConfigurationProperties 该注解可以将SpringBoot中的配置文件（application.properties、application.yml）中的配置项读取到一个对象中 步骤 创建名为application的properties文件 jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/springbootdb?serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456 创建JdbcProperties配置类 @ConfigurationProperties(prefix = &quot;jdbc&quot;) public class JdbcProperties { private String driverClassName; private String url; private String username; private String password; public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } 导入依赖 &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-configuration-processor&lt;/artifactId&gt; &lt;optional&gt;true&lt;/optional&gt; &lt;/dependency&gt; 创建JdbcConfig配置类 @Configuration @EnableConfigurationProperties(JdbcProperties.class) public class JdbcConfig { @Bean public DataSource getDataSource(JdbcProperties jdbcProperties){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(jdbcProperties.getDriverClassName()); druidDataSource.setUrl(jdbcProperties.getUrl()); druidDataSource.setUsername(jdbcProperties.getUsername()); druidDataSource.setPassword(jdbcProperties.getPassword()); return druidDataSource; } } 优化 移除 JdbcProperties 配置类 直接在 JdbcConfig 的方法上加注解 @Configuration public class JdbcConfig { @Bean @ConfigurationProperties(prefix = &quot;jdbc&quot;) public DataSource getDataSource(){ return new DruidDataSource(); } } ④. 多个yml文件配置（多个properties文件配置） 单个yaml文件中的多个yaml配置 单个yaml文件可以使用 --- 对多个配置进行分类 --- # 开发环境配置信息 server: port: 8080 spring: profiles: dev --- # 测试环境配置信息 server: port: 8080 spring: profiles: test --- # 生产环境配置信息 server: port: 8080 spring: profiles: pro # 激活对应配置信息 spring: profiles: actice: pro 多个yml文件的配置 / 多个properties文件配置 SpringBoot中允许多个 yml / properties 配置文件，这些配置文件的名称必须为 application-***.yml application-***.properties并且这些配置文件必须要在 application.yml application.properties中激活之后才可以使用 如果properties和yml配置文件同时存在SpringBoot项目中，那么这两种文件都有效，在两个配置文件中如果存在同名的配置项，以properties文件为主 激活其他配置文件的方式 # yaml激活写法 spring: profiles: active: dev # properties文件激活写法 spring.profiles.active=pro 以上都是使用profile的配置方式激活对应配置文件，profile的激活方式共有以下几种： 配置文件 虚拟机参数 -Dspring.profile.active=pro 命令行参数 --spring.profile.active=pro java -jar 项目打包后的文件名 --spring.profile.active=pro ⑤. lombok应用 lombok是一个插件工具类包，提供了一些注解 @Data @Getter @Setter 等这些注解去简化实体类中的构造方法、get/set等方法的编写 步骤 在IDEA中安装lombok插件 添加lombok对应的依赖 使用lombok的注解改造实体类 注解 @Data：自动提供getter、setter、hashCode、equals、toString方法 @Getter：自动提供getter方法 @Setter：自动提供setter方法 @Slf4j：自动在bean中提供log变量 实现 导入依赖 &lt;dependency&gt; &lt;groupId&gt;org.projectlombok&lt;/groupId&gt; &lt;artifactId&gt;lombok&lt;/artifactId&gt; &lt;version&gt;1.18.16&lt;/version&gt; &lt;scope&gt;provided&lt;/scope&gt; &lt;/dependency&gt; 创建实体类 @Data @Slf4j public class User { private int id; private String username; private String password; private Date jointime; } ⑥. 修改SpringMVC端口和静态资源 修改Tomcat端口 server: port: 80 servlet: context-path: /hello 访问项目中的静态资源 classpath:/META-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/ ⑦. SpringMVC拦截器配置 日志记录级别 logging: level: com.dnc: debug org.springframework: info 编写自定义拦截器 @Slf4j public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.debug(&quot;MyInterceptor的preHandle方法&quot;); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.debug(&quot;MyInterceptor的postHandle方法&quot;); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.debug(&quot;MyInterceptor的afterCompletion方法&quot;); } } 编写拦截器配置类，重写方法添加拦截器功能 @Configuration public class MvcConfig implements WebMvcConfigurer { @Bean public MyInterceptor myInterceptor(){ return new MyInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { //添加拦截器，拦截所有请求 registry.addInterceptor(myInterceptor()).addPathPatterns(&quot;/*&quot;); } } ⑧. 事务和连接池 事务配置 添加事务相关依赖，mysql相关依赖 编写业务UserService使用事务注解 @Transactional 数据库连接池 hikari 配置 SpringBoot集成的默认数据库连接池，只需要我们配置就好了，据说效率比druid高 步骤 &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-jdbc&lt;/artifactId&gt; &lt;/dependency&gt; 数据库连接池默认使用hikari，在配置文件中配置如下就可以了 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springbootdb?serverTimezone=Asia/Shanghai username: root password: 123456 ⑨. SpringBoot整合MyBatis 添加启动器依赖 &lt;dependency&gt; &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt; &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt; &lt;version&gt;2.1.3&lt;/version&gt; &lt;/dependency&gt; 配置MyBatis：实体类别名包，日志，映射文件等 mybatis: #配置实体类别名包路径 type-aliases-package: com.dnc.entity #配置映射文件路径 mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 配置MapperScan @SpringBootApplication @MapperScan(&quot;com.dnc.mapper&quot;) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } ⑩. SpringBoot通用Mapper 添加启动器依赖 &lt;dependency&gt; &lt;groupId&gt;tk.mybatis&lt;/groupId&gt; &lt;artifactId&gt;mapper-spring-boot-starter&lt;/artifactId&gt; &lt;version&gt;2.1.5&lt;/version&gt; &lt;/dependency&gt; 改造UserMapper继承 Mapper 修改启动引导类Application中的Mapper扫描注解 修改User实体类添加JPA注解 改造UserService实现业务功能 此处注意一个问题，实体类中所有成员属性的类型必须为引用类型，基本类型会导致SQL无法执行 3. SpringBoot整合测试 http://localhost/user/1 { &quot;id&quot;: 1, &quot;username&quot;: &quot;絷缘&quot;, &quot;password&quot;: &quot;123456&quot;, &quot;jointime&quot;: &quot;2021-03-13 23:31:23&quot; } 4. SpringBoot整合Junit ①. 添加启动器依赖 spring-boot-starter-test &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt; &lt;/dependency&gt; ②. 编写测试代码 @RunWith(SpringRunner.class) @SpringBootTest(classes=Application.class) public class UserServiceTest { @Autowired private UserService userService; @Test public void queryById() { User user = userService.queryById(1); System.out.println(user); } } //输出结果 User(id=1, username=絷缘, password=123456, jointime=Sat Mar 13 23:31:23 CST 2021) 5. SpringBoot整合Redis ①. 添加启动器依赖 spring-boot-starter-data-redis &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt; &lt;/dependency&gt; ②. 配置application.yml中修改redis的连接参数 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springbootdb?serverTimezone=Asia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 ③. 编写测试类引用RedisTemplate操作redis中的五种数据类型 string hash list set sorted set @RunWith(SpringRunner.class) @SpringBootTest public class RedisTest { @Autowired private RedisTemplate redisTemplate; @Test public void test(){ //操作string类型 redisTemplate.opsForValue().set(&quot;username&quot;,&quot;絷缘&quot;); redisTemplate.boundValueOps(&quot;name&quot;).set(&quot;絷缘&quot;); System.out.println(&quot;username=&quot; + redisTemplate.opsForValue().get(&quot;username&quot;)); System.out.println(&quot;name=&quot; + redisTemplate.opsForValue().get(&quot;name&quot;)); //操作hash散列 redisTemplate.boundHashOps(&quot;user&quot;).put(&quot;username&quot;,&quot;zhiyuan&quot;); redisTemplate.boundHashOps(&quot;user&quot;).put(&quot;nickname&quot;,&quot;絷缘&quot;); redisTemplate.boundHashOps(&quot;user&quot;).put(&quot;password&quot;,&quot;123456&quot;); System.out.println(&quot;username=&quot; + redisTemplate.opsForHash().get(&quot;user&quot;,&quot;username&quot;)); System.out.println(&quot;nickname=&quot; + redisTemplate.opsForHash().get(&quot;user&quot;,&quot;nickname&quot;)); System.out.println(&quot;password=&quot; + redisTemplate.opsForHash().get(&quot;user&quot;,&quot;password&quot;)); Set userkeys = redisTemplate.boundHashOps(&quot;user&quot;).keys(); System.out.println(&quot;userkeys=&quot; + userkeys); List uservalues = redisTemplate.boundHashOps(&quot;user&quot;).values(); System.out.println(&quot;uservalues=&quot; + uservalues); //操作list集合 redisTemplate.boundListOps(&quot;person&quot;).leftPush(&quot;personA&quot;); redisTemplate.boundListOps(&quot;person&quot;).leftPush(&quot;personB&quot;); redisTemplate.boundListOps(&quot;person&quot;).leftPush(&quot;personC&quot;); List person = redisTemplate.boundListOps(&quot;person&quot;).range(0, -1); System.out.println(&quot;person=&quot; + person); //操作set集合 redisTemplate.boundSetOps(&quot;animals&quot;).add(&quot;dog&quot;,&quot;cat&quot;,&quot;pig&quot;,&quot;horse&quot;); Set animals = redisTemplate.boundSetOps(&quot;animals&quot;).members(); System.out.println(&quot;animals=&quot; + animals); //操作sortedset集合 redisTemplate.boundZSetOps(&quot;student&quot;).add(&quot;studentA&quot;,22); redisTemplate.boundZSetOps(&quot;student&quot;).add(&quot;studentB&quot;,25); redisTemplate.boundZSetOps(&quot;student&quot;).add(&quot;studentC&quot;,21); redisTemplate.boundZSetOps(&quot;student&quot;).add(&quot;studentD&quot;,26); Set student = redisTemplate.boundZSetOps(&quot;student&quot;).range(0, -1); System.out.println(&quot;student=&quot; + student); } } //输出结果 username=絷缘 name=絷缘 username=zhiyuan nickname=絷缘 password=123456 userkeys=[username, nickname, password] uservalues=[zhiyuan, 絷缘, 123456] person=[personC, personB, personA, personC, personB, personA, personC, personB, personA] animals=[dog, pig, cat, horse] student=[studentC, studentA, studentB, studentD] 6. SpringBoot项目部署 目标：将SpringBoot项目使用maven指令打成jar包并运行测试 将项目中的资源、配置、依赖包打到一个jar包中：使用 maven 的 package 命令 部署jar包：java -jar 包名 &lt;build&gt; &lt;resources&gt; &lt;resource&gt; &lt;directory&gt;src/main/resources&lt;/directory&gt; &lt;includes&gt; &lt;include&gt;**/*.xml&lt;/include&gt; &lt;/includes&gt; &lt;/resource&gt; &lt;/resources&gt; &lt;plugins&gt; &lt;plugin&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt; &lt;configuration&gt; &lt;mainClass&gt;com.xzy.youpin.Application&lt;/mainClass&gt; &lt;/configuration&gt; &lt;executions&gt; &lt;execution&gt; &lt;goals&gt; &lt;goal&gt;repackage&lt;/goal&gt; &lt;/goals&gt; &lt;/execution&gt; &lt;/executions&gt; &lt;/plugin&gt; &lt;/plugins&gt; &lt;/build&gt; 安装插件 JBLSpringBootAppGen，可以自动创建 Application.java 和 application.yml 九、thymeleaf 十、 SpringSecurity RBAC（Role-Based-Access-Control）：基于角色的权限管理 认证 + 授权 1. 步骤 ①. HttpBasic模式的认证 ②. FormLogin模式的认证 编写login.html文件 创建配置类继承 WebSecurityConfigurerAdapter 重写 configure(HttpSecurity http) 配置登录验证逻辑 重写 configure(AuthenticationManagerBuilder auth) 实现内存身份验证 运行验证 ③. 前后端分离架构的认证 十一、MyBatis-Plus 1. 导包 &lt;dependency&gt; &lt;groupId&gt;com.baomidou&lt;/groupId&gt; &lt;artifactId&gt;mybatis-plus&lt;/artifactId&gt; &lt;version&gt;mybatis-plus-latest-version&lt;/version&gt; &lt;/dependency&gt; &lt;!-- https://mvnrepository.com/artifact/com.alibaba/druid --&gt; &lt;dependency&gt; &lt;groupId&gt;com.alibaba&lt;/groupId&gt; &lt;artifactId&gt;druid&lt;/artifactId&gt; &lt;version&gt;1.2.6&lt;/version&gt; &lt;/dependency&gt; &lt;!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --&gt; &lt;dependency&gt; &lt;groupId&gt;mysql&lt;/groupId&gt; &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;version&gt;8.0.21&lt;/version&gt; &lt;/dependency&gt; &lt;!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --&gt; &lt;dependency&gt; &lt;groupId&gt;org.projectlombok&lt;/groupId&gt; &lt;artifactId&gt;lombok&lt;/artifactId&gt; &lt;version&gt;1.18.12&lt;/version&gt; &lt;/dependency&gt; &lt;!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 --&gt; &lt;dependency&gt; &lt;groupId&gt;org.slf4j&lt;/groupId&gt; &lt;artifactId&gt;slf4j-log4j12&lt;/artifactId&gt; &lt;version&gt;1.7.25&lt;/version&gt; &lt;/dependency&gt; 2. 配置log4j log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.Layout.ConversionPattern=[%t] [%c]-[%p] %m%n 3. SpringBoot整合Mybatis-Plus &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt; &lt;scope&gt;test&lt;/scope&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.baomidou&lt;/groupId&gt; &lt;artifactId&gt;mybatis-plus-boot-starter&lt;/artifactId&gt; &lt;version&gt;Latest Version&lt;/version&gt; &lt;/dependency&gt; queryWrapper.lt（）——小于 queryWrapper.le（）——小于等于 queryWrapper.gt（）——大于 queryWrapper.ge（）——大于等于 queryWrapper.eq（）——等于 queryWrapper.ne（）——不等于 queryWrapper.betweeen（“age”,10,20）——age在值10到20之间 queryWrapper.notBetweeen（“age”,10,20）——age不在值10到20之间 queryWrapper.like（“属性”,“值”）——模糊查询匹配值‘%值%’ queryWrapper.notLike（“属性”,“值”）——模糊查询不匹配值‘%值%’ queryWrapper.likeLeft（“属性”,“值”）——模糊查询匹配最后一位值‘%值’ queryWrapper.likeRight（“属性”,“值”）——模糊查询匹配第一位值‘值%’ queryWrapper.isNull（）——值为空或null queryWrapper.isNotNull（）——值不为空或null queryWrapper.in（“属性”，条件，条件 ）——符合多个条件的值 queryWrapper.notIn(“属性”，条件，条件 )——不符合多个条件的值 queryWrapper.or（）——或者 queryWrapper.and（）——和 queryWrapper.orderByAsc(“属性”)——根据属性升序排序 queryWrapper.orderByDesc(“属性”)——根据属性降序排序 queryWrapper.inSql(“sql语句”)——符合sql语句的值 queryWrapper.notSql(“sql语句”)——不符合SQL语句的值 queryWrapper.esists（“SQL语句”）——查询符合SQL语句的值 queryWrapper.notEsists（“SQL语句”）——查询不符合SQL语句的值 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/pILxU31oU/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/pILxU31oU/"},{"title":"JavaSE学习","content":"1. 关键字 A.理解： Java中事先定义的，含有特别意义的标识符 private 一种访问控制方式：私有模式 protected 一种访问控制方式：保护模式 public 一种访问控制方式：公有模式 abstract 表明类或者成员方法具有抽象属性 class 类 extends 表明一个类型是另一个类型的子类型，这里常见的类型有类和接口 final 用来说明最终属性，表明一个类不能派生出子类，或者成员方法不能被覆盖，或者成员域的值不能被改变 implements 表明一个类实现了给定的接口 interface 接口 native 用来声明一个方法是由与计算机相关的语言（如C/C++/FORTRAN语言）实现的 new 用来创建新实例对象 static 表明具有静态属性 strictfp 用来声明FP_strict（单精度或双精度浮点数）表达式遵循IEEE 754算术规范 synchronized 表明一段代码需要同步执行 transient 声明不用序列化的成员域 volatile 表明两个或者多个变量必须同步地发生变化 break 提前跳出一个块 continue 回到一个块的开始处 return 从成员方法中返回数据 do 用在do-while循环结构中 while 用在循环结构中 if 条件语句的引导词 else 用在条件语句中，表明当条件不成立时的分支 for 一种循环结构的引导词 instanceof 用来测试一个对象是否是指定类型的实例对象 switch 分支语句结构的引导词 case 用在switch语句之中，表示其中的一个分支 default 默认，例如，用在switch语句中，表明一个默认的分支 try 尝试一个可能抛出异常的程序块 catch 用在异常处理中，用来捕捉异常 throw 抛出一个异常 throws 声明在当前定义的成员方法中所有需要抛出的异常 import 表明要访问指定的类或包 package 包 boolean 基本数据类型之一，布尔类型 byte 基本数据类型之一，字节类型 char 基本数据类型之一，字符类型 double 基本数据类型之一，双精度浮点数类型 float 基本数据类型之一，单精度浮点数类型 int 基本数据类型之一，整数类型 long 基本数据类型之一，长整数类型 short 基本数据类型之一,短整数类型 null 空 true 正确 false 错误 super 表明当前对象的父类型的引用或者父类型的构造方法 this 指向当前实例对象的引用 void 声明当前成员方法没有返回值 goto 保留关键字，没有具体含义 const 保留关键字，没有具体含义 2. 保留字 A. 理解：Java中事先定义的以后的版本可能被使用的标识符 goto 和 const 3. 标识符 A. 理解：用来表示类名，变量名，方法名，类型名，数组名，文件名的有效字符序列称为标识符（通俗讲：自己起的名字） 组成： 数字0-9 字母A-Za-z 下划线_ 美元符$ 注意事项： 不能以数字开头，阿里巴巴手册中规定不能以下划线或$开头 命名遵守驼峰命名法 大驼峰：类名 小驼峰：方法名，变量名 Java中严格区分大小写 结尾必须以分号结束 写代码时注意缩进，等号前后加空格 4. 注释 A. 理解：用于解释说明程序的文字信息 B. 类别： 单行注释：// 多行注释：/**/ 文档注释：/***/ 5. 进制转换 A. 二进制 由0，1组成。以0b开头 int num = 0b11011; B. 八进制 由0,1,...7组成。以0开头 int num = 01011; C. 十六进制 由0,1,...9,a,b,c,d,e,f(大小写均可)组成，以0x开头 int num = 0xac34b; D. 十进制 由0,1,...9组成，默认整数是十进制 int num = 110; 6. 变量 A. 理解：内存中的一块拥有名称可以存储数据的存储区域 内存中的一个存储区域 该区域有自己的名称（变量名）、类型（数据类型）、值 Java中每个变量必须先声明，再赋值，然后才能使用 该区域的数据可以在同一类型范围内不断变化 Java 中的变量有四个基本属性：变量名，数据类型，存储单元和变量值 B. 变量的声明：数据类型 变量名 = 变量值 C. 变量必须初始化才能使用，初始化就是给一个初始值 7. 常量 A. 理解：在程序中固定不变的值，是不能改变的数据。 B. 字面常量：（值不会变化的量） 整型常量（int） 浮点数常量（float double） ：2e3f 3.6d 0f 3.84d 5.022e+23f 布尔常量（boolean）：true false 字符常量（char）：'a' '1' '&amp;' '\\r' '\\u0000' 字符串常量（String）：&quot;HelloWorld&quot; &quot;123&quot; &quot;We come \\n XXX&quot; null常量（null）：null C. 自定义常量： 8. 数据类型 A. 基本数据类型 类型 型别 字节 取值范围 byte 整型 1byte（8位） -27 ~ 27-1（-128~127，默认值0）1000 0000固定为-128 short 整型 2byte（16位） -215 ~ 215-1（-32768~32717，默认值0）1000 0000 0000 0000固定为-32768 int 整型 4byte（32位） -231 ~ 231-1（-2147483648~2147483647，默认值0） long 整型 8byte（64位） -263 ~ 263-1（默认值0L）赋值时要以L结尾 float 浮点型 4byte（32位） +3.402823e+38 ~ 1.401298e-45（默认值0）赋值时要以F结尾 double 浮点型 8byte（64位） 1.797693e+308~ 4.9000000e-324（默认值0）以D结尾，java中浮点型默认为Double，所以不用加D char 文本型 2byte（16位） 0 ~ 216-1（取值范围0~65535，默认值为空） boolean 布尔型 1byte（8位） true/false B. 引用数据类型 字符串 数组 类 接口 Lambda C. 注意：在数据做为参数传递的时候，基本数据类型是值传递，引用数据类型是引用传递（地址传递） D. 数据类型转换 默认转换（小转大，小的数据类型的值，赋给大的数据类型的变量） 从低精度向高精度转换 long-&gt;float的自动转型参考：https://blog.csdn.net/writing_happy/article/details/78880606 浮点数在内存中的存储形式参考：https://blog.csdn.net/u014470361/article/details/79820892 public class DataTypeDemo{ public static void main(String [] args){ System.out.println(1024); //这是一个整数，整数有byte、short、int、long四种，java中默认为int System.out.println(3.14); //这是一个浮点数，浮点数有float、double两种，java中默认为double long num1 = 1024L; //加了L，为long类型 System.out.println(num1); long num2 = 1024; //未加L，默认为int类型，依然可以正常输出，中间发生了自动转换，int到long System.out.println(num2); double num3 = 2.5F; //加了F，为float类型，发生自动转换，float到double System.out.println(num3); double num4 = 2.5; //未加F，默认为double类型 System.out.println(num4); float num5 = 30L; //加了L，为long类型，依然可以正常输出，中间发生了自动转换，long到float System.out.println(num5); byte num6 = 25; int num7 = num6; System.out.println(num7); //可以正常输出，发生了自动类型的转换，byte到int byte num8 = 35; short num9 = num8; System.out.println(num9); //可以正常输出，发生了自动类型转换，byte到short short num10 = 100; int num11 = num10; System.out.println(num11); //可以正常输出，发生了自动类型转换，short到int } } 强制转换的格式：大数据类型 变量名称 = (大数据类型)(小数据类型) 注意：强制转换的格式是在需要转型的数据前加上“( )”，然后在括号内加入需要转化的数据类型。数据经过转型运算后，精度会丢失 char不能转换为short和byte public static void main(String[] args){ int num = 127; byte by = 122; by = (byte)num; } byte/short/char 默认都可以发生数学运算，在运算的时候会被提升为int类型计算 public static void main(String [] args){ char c = 'A'; // A的ASCII码为65 System.out.println(c + 1); // 66 } byte b1 = 20; byte b2 = 22; int b = b1 + b2; //byte被提升为int型并进行了数学运算，计算结果也是int short s1 = 34; short s2 = 32; int s = s1 + s2; //short被提升为int型并进行了数学运算，计算结果也是int 布尔类型不能发生数据类型转换 在ASCII码表中，48表示字符0，65表示大写A，97表示小写a JDK9新特性：“ 编译器常量优化 ”：当两个常量运算时，如果运算结果数值没有超过左侧变量类型的范围，则正常输出，右侧一旦出现变量，就无法参与这项优化 9. 字符集和字符编码 A. 为了实现计算机认识字母、符号、图形 B. 字符集就是字母数字指定的二进制编码 C. 字符集制成的表示字符编码表 ASCII GBK GB2312 w3c出面制成统一码表：unicode码表 按照进制制成新的码表 utf-8 utf-16 utf-32 10. 运算符 A. 算数运算符 / % ++ 前++：先运算再赋值 后++：先赋值再运算 -- 前--：先运算再赋值 后--：先赋值再运算 public static void main(String [] args){ int num = 999; int num1 = 300; System.out.println(num+num1); System.out.println(num-num1); System.out.println(num*num1); System.out.println(num/num1); System.out.println(num%num1); System.out.println(num1%num);//小的数取模于大的数，得到的是本身（小的数） int num2 = 3; num2++; num2--; } B. 赋值运算符 = += -= *= /= %= 补充：b+=4不等于b=b+4，扩展的赋值运算符都隐含了强制类型转换 ，等价于b=(b的数据类型)b+4； instanceof：判断是否是类的对象 &quot;String&quot; instanceof String public static void main(String [] args){ String name = &quot;zhiyuan&quot;; int sum = 666; sum = sum + 20; sum += 20; //上边的简写形式 } C. 比较（关系）运算符 &gt; &lt; &gt;= &lt;= != == 基本数据类型，比较值是否相等 引用数据类型，比较地址值是否相等 和equals的区别 运算结果为boolean值 public static void main(String [] args){ System.out.println(100 &gt; 20); System.out.println(100 &lt; 200); System.out.println(100 &gt;= 200); System.out.println(100 &lt;= 200); System.out.println(100 != 200); System.out.println(100 == 200); } D. 逻辑运算符（左右两侧均为布尔表达式） &amp;：逻辑与 |：逻辑或 &amp;&amp;：左右两侧都成立，结果成立（全真为真，有假为假）（短路与） || ：左右两侧有一个成立，结果成立（有真为真，全假为假），前面成立，后面无需看结果（短路或） ！：取反 public static void main(String [] args){ System.out.println(100 &gt; 20 &amp;&amp; 200 &gt; 300); System.out.println(100 &lt; 200 &amp;&amp; 200 &lt; 100); System.out.println(100 &gt; 20 || 200 &gt; 300); System.out.println(100 &lt; 200 || 200 &lt; 100); System.out.println(!(100 &gt; 20)); } E. 位运算符（位运算是最快的） &amp;：（二进制补码按位与） | ：（二进制补码按位或） ^：（二进制补码按位异或）一个数对另外一个数按位异或两遍，还是它本身 &lt;&lt;：（二进制按位左移）次方运算直接左移（次方-1）位 12 &lt;&lt; 2 12*2^2 = 48 &gt;&gt;：（二进制按位右移） 12 &gt;&gt; 2 12/2^2 = 3 ~ &gt;&gt;&gt;：无符号右移规则和右移运算是一样的，只是填充时不管左边的数字是正是负都用0来填充，无符号右移运算只针对负数计算，因为对于正数来说这种运算没有意义 public static void main(String [] args){ System.out.println(8 &amp; 3); System.out.println(8 | 3); System.out.println(8 ^ 3); System.out.println(8 &lt;&lt; 3); System.out.println(8 &gt;&gt; 3); } F. 三元（三目）运算符 表达式 ? 成立结果 : 不成立结果 public static void main(String [] args){ System.out.println(8 &gt; 3 ? true : false); } G. instanceof运算符 java中，instanceof运算符的前一个操作符是一个引用变量，后一个操作数通常是一个类（可以是接口），用于判断前面的对象是否是后面的类，或者其子类、实现类的实例。如果是返回true，否则返回false。 11. 转义字符 转义字符 描述 \\r 回车符 \\n 换行符 \\t 制表符 \\b 退格符 \\' 单引号字符 \\'' 双引号字符 \\\\ 反斜杠字符 12. 流程控制语句 A. 顺序语句：代码执行的时候是从上到下，从左到右的 B. 分支语句： if语句 public class ControlDemo { public static void main(String[] args) { int num = 10; if(num &gt; 5){ System.out.println(num + &quot; &gt; 5&quot;); } } } if-else语句 public class ControlDemo { public static void main(String[] args) { int num = 3; if(num &gt; 5){ System.out.println(num + &quot; &gt; 5&quot;); }else{ System.out.println(num + &quot; &lt; 5&quot;); } } } if-else-if-else语句 // if else if else语句 public class ControlDemo { public static void main(String[] args) { int age = 20; double height = 120.0; if(age &gt; 10){ System.out.println(age + &quot; &gt; 10&quot;); }else if(height &lt; 120){ System.out.println(height + &quot; &lt; 120&quot;); }else{ System.out.println(age + &quot; &lt; 10&quot; + height + &quot; &gt; 120&quot;); } } } import java.util.Scanner; public class ScannerDemo { public static void main(String[] args) { // 根据用户输入的成绩，判断是哪一个等级 Scanner sc = new Scanner(System.in);//System.in系统的标准输入 // String str = sc.next(); //String str = sc.nextLine(); int score = sc.nextInt(); System.out.println(score); if (score &gt; 90) { System.out.println(&quot;优&quot;); }else if(score &gt; 80){ System.out.println(&quot;甲&quot;); }else if(score &gt; 70){ System.out.println(&quot;乙&quot;); }else if(score &gt; 60){ System.out.println(&quot;丙&quot;); }else { System.out.println(&quot;丁&quot;); } } } switch语句（break不可省略） import java.util.Scanner; public class ControlDemo { public static void main(String[] args) { //switch语句（int、char、byte、short、String【JDK1.7新增】） Scanner sc = new Scanner(System.in); int i = sc.nextInt(); switch(i){ case 1: System.out.println(&quot;星期一&quot;);break; case 2: System.out.println(&quot;星期二&quot;);break; case 3: System.out.println(&quot;星期三&quot;);break; case 4: System.out.println(&quot;星期四&quot;);break; case 5: System.out.println(&quot;星期五&quot;);break; case 6: System.out.println(&quot;星期六&quot;);break; case 7: System.out.println(&quot;星期日&quot;);break; default: System.out.println(&quot;没有&quot;);break; } } } C. 循环语句： 注意：如果出现死循环，程序不能及时停止，会出现值栈溢出Stack Overflow（内存溢出） 所以在使用while循环的时候，我们要在适当的时侯终止循环，结合if else使用break终止循环，直接退出循环最外层，不再执行循环 break是直接终止循环 continue是终止本次循环，进行下次循环，直到循环执行结束 while（布尔表达式）{循环体} public class WhileDemo { public static void main(String[] args) { //while循环 控制循环条件的变量值，固定循环次数 int flag = 10; while(flag &gt; 0){ System.out.println(&quot;循环正常执行&quot;); flag --; } } } do {循环体} while（布尔表达式） public class DoWhileDemo { public static void main(String[] args) { public class DoWhileDemo { public static void main(String[] args) { //do while 循环 即使条件不成立，也会执行一次 int num = 10; do{ System.out.println(&quot;循环正常执行&quot; + num); num --; }while(num &gt; 0); } } for（初始化语句；循环条件；迭代体）{循环体} public class ForDemo { public static void main(String[] args) { //for循环 循环次数是初始变量值和循环条件计算得出的 //i++和++i在for循环中没有区别 for (int i = 0;i &lt;= 10;i++){ System.out.println(i); } } } 死循环 for(;;){} while(true){} do{}while(true); print 和 println的区别 print：直接输出 println：换行输出 D. 循环嵌套： 我们可以使用循环去做很多事情 打印九九乘法表 public class CombineForDemo { public static void main(String[] args) { //九九乘法表 //外层循环控制行 for(int i = 1;i &lt;= 9;i++){ //内层循环控制列 for(int k = 1;k &lt;= i;k++){ System.out.print(k+&quot;×&quot;+i+&quot;=&quot;+i*k+&quot;\\t&quot;); } System.out.println(&quot;&quot;); } } } 1×1=1 1×2=2 2×2=4 1×3=3 2×3=6 3×3=9 1×4=4 2×4=8 3×4=12 4×4=16 1×5=5 2×5=10 3×5=15 4×5=20 5×5=25 1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36 1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49 1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64 1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81 输出菱形 public class DiamondDemo { public static void main(String[] args) { //菱形 for (int i = 1;i &lt;= 5;i++){ for (int k = 5 - i;k &gt;= 0;k--){ System.out.print(&quot; &quot;); } for (int j = 1;j &lt;= 2*i-1;j++){ System.out.print(&quot;*&quot;); } System.out.println(&quot;&quot;); } for (int i = 4;i &gt;= 1;i--){ for (int k = 1; k &lt;= 6-i;k++){ System.out.print(&quot; &quot;); } for (int j = 1;j &lt;= 2*i-1;j++){ System.out.print(&quot;*&quot;); } System.out.println(&quot;&quot;); } } } 输出等腰三角形 public class IsoscelesTriangleDemo { public static void main(String[] args) { //等腰三角形 int num = 6; for(int i = 0;i &lt;= num;i++){ //打印空格占位 //前半边占位的空格和行数的关系，是总的行数-1 for(int k = num - i;k &gt;= 0;k--){ System.out.print(&quot; &quot;); } //打印后半部分星号 for(int j = 1;j &lt;= 2*i-1;j++){ System.out.print(&quot;*&quot;); } //负责换行 System.out.println(&quot;&quot;); } } } 求1000以内的水仙花数 public class DaffodilsNumDemo { public static void main(String[] args) { //水仙花数 for(int i = 100;i &lt; 1000;i++){ int onesplace = i % 10; //个位 int hundredsplace = i / 100; //百位 int tenplace = i / 10 % 10; //十位 int newvalue = onesplace*onesplace*onesplace + tenplace*tenplace*tenplace + hundredsplace*hundredsplace*hundredsplace; if(newvalue == i){ System.out.println(&quot;水仙花数：&quot; + i); } } } } 求1000以内的素数 public class PrimeNumberDemo { public static void main(String[] args) { for(int i = 2;i &lt; 1000;i++){ for(int j = 2;j &lt; i;j++){ if(i % j == 0){ break; }else{ System.out.println(i+&quot;为素数&quot;); break; } } } } } 求100以内的7的倍数 public class SevenTimesDemo { public static void main(String[] args) { for(int i = 0;i &lt; 100;i++){ if(i % 7 == 0){ System.out.println(i+&quot;是7的倍数&quot;); } } } } 今有雉兔同笼，上有三十五头，下有九十四足，问雉兔各几何？ public class ChickenRabbitCageDemo { public static void main(String[] args) { int count = 35; int foot = 94; int chickencount; int rabbitcount; for(chickencount = 1;chickencount &lt;= 35;chickencount++){ rabbitcount = count - chickencount; if (chickencount*2+rabbitcount*4 == 94) { System.out.println(&quot;鸡有&quot;+chickencount+&quot;只&quot;); System.out.println(&quot;兔有&quot;+rabbitcount+&quot;只&quot;); break; } } } } 有一对兔子，从出生后第3个月起每个月都生一对兔子，小兔子长到第三个月后每个月又生一对兔子，假如兔子都不死，问一年后的兔子总数为多少？（斐波那契数列） 1 1 2 3 5 8 13 21 int month = 12; int first = 1; int second = 1; int count = 0; for(int i = 3;i &lt;= month;i++){ count = first + second; first = second; second = count; System.out.println(i+&quot;月份生的兔子的总数是：&quot;+count*2); //288 } 3 3 3 3 3 3 2 2 2 3 3 2 1 2 3 3 2 2 2 3 3 3 3 3 3 for(int i = 0;i &lt; 7;i++){ if(i==0 &amp;&amp; i == 13){ System.out.println(&quot;7&quot;); } } E. Debug调试工具 F. 指定跳转的位置（java的标签） 结合循环的break使用，指定程序中断跳出到循环外的位置 break wc:for(int i = 0;i &lt; 4;i++){ nc:for(int j = 0;j &lt; 5;j++){ if(j == 3){ //break; //break nc; break wc; } System.out.println(&quot;*&quot;); } System.out.println(); } //输出结果 *** continue wc:for(int i = 0;i &lt; 4;i++){ nc:for(int j = 0;j &lt; 5;j++){ if(j == 3){ //continue; //continue nc; continue wc; } System.out.println(&quot;*&quot;); } System.out.println(); } //输出结果 ************ return也可以结束循环，不过本质是结束方法 13. 数组 A. 数组只能保存一种类型的数据 B. 数组只要声明了，那么数组的长度在程序运行过程中就是固定不可变化的，即使移除数组的值 C. 数组的声明 静态声明：在声明数组的时候，给数组将值添加进去，然后由系统计算数组的长度 int [] arr = new int[]{0,1,2,3,4,5,6,7,8,9}; 动态声明：在声明数组的时候，固定数组的长度，然后由系统添加数组中的初始值 int [] arr = new int[10]; 基本类型各自初始化值 int类型，默认初始值为0 long类型，默认初始值为0 short类型，默认初始值为0 byte类型，默认初始值为0 char类型，默认初始值为\\u0000（unicode字符，不可见） float类型，默认初始值为0.0 double类型，默认初始值0.0 boolean类型，默认值false 引用数据类型，默认初始值为null（空常量） 数组是引用数据类型 D. 一维数组 数组的声明 静态声明：数据类型 [] 变量名称 = new 数据类型[] {值1，值2，值3} 动态声明：数据类型 [] 变量名称 = new 数据类型[数组的长度]； 数组声明的简写形式：数据类型 [] 变量名称 = {值1，值2，值3} length表示数组的长度 E. 数组的遍历 public class ArrayListDemo { public static void main(String[] args) { int [] arr = new int[]{10,12,13,14,15,16}; System.out.println(arr[0]); for (int i = 0; i &lt; arr.length; i++) { System.out.println(&quot;arr[&quot; + i + &quot;]=&quot; + arr[i]); } //增强for循环（foreach） //for（数组中元素的数据类型 i代表的是下表位的元素的值 ：数组或者集合） for (int i : arr) { System.out.println(i); } } } //运行结果 10 arr[0]=10 arr[1]=12 arr[2]=13 arr[3]=14 arr[4]=15 arr[5]=16 10 12 13 14 15 16 练习：int [] arr = new int [] {21,3,41,45,67,2,78,6};中的最大值与最小值 public static void main(String [] args){ for (int i = 0; i &lt; arr.length ; i++) { for(int k = i+1; j &lt;= arr.length;j++) { if(arr[i] &gt; arr[j]){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } for(int k : arr){ System.out.println(i); } } 最大值为：78 最小值为：2 public class ArrayMaxMinDemo { public static void main(String[] args) { int [] arr = new int []{21,3,41,45,67,2,78,6}; //从数组中拿任意一个数 //进行数组中其他元素比较，按照比较的结果进行换位 int num = arr[0]; for (int i = 0; i &lt; arr.length; i++) { if(num &gt; arr[0]){ num = arr[0]; } } System.out.println(&quot;最小值为：&quot;+num); } } F. 二维数组 一维数组的下标位依然是一个一维数组，这种数组就是二维数组 二维数组的声明 数据类型 [] [] 变量名称 = new int [] []{}; 数据类型 [] [] 变量名称 = new int [3] [4]; public class SecondArrayDemo { public static void main(String[] args) { int [][] arr = new int[][]{{2,3,4},{5,6,7},{8,9,10}}; int [][] arr1 = new int[3][4]; //获取值 // System.out.println(arr[0][0]); // System.out.println(arr[0][1]); // System.out.println(arr[0][2]); // // System.out.println(arr[1][0]); // System.out.println(arr[1][1]); // System.out.println(arr[1][2]); // // System.out.println(arr[2][0]); // System.out.println(arr[2][1]); // System.out.println(arr[2][2]); arr1[0][0] = 11; arr1[0][1] = 12; arr1[0][2] = 13; arr1[0][3] = 14; arr1[1][0] = 24; arr1[1][1] = 23; arr1[1][2] = 22; arr1[1][3] = 21; arr1[2][0] = 51; arr1[2][1] = 52; arr1[2][2] = 53; arr1[2][3] = 54; // System.out.println(arr1[0][0]); // System.out.println(arr1[0][1]); // System.out.println(arr1[0][2]); // System.out.println(arr1[0][3]); // // System.out.println(arr1[1][0]); // System.out.println(arr1[1][1]); // System.out.println(arr1[1][2]); // System.out.println(arr1[1][3]); // // System.out.println(arr1[2][0]); // System.out.println(arr1[2][1]); // System.out.println(arr1[2][2]); // System.out.println(arr1[2][3]); for (int i = 0 ; i &lt; arr.length ; i++){ for(int j = 0 ; j &lt; arr[i].length ; j++){ System.out.println(&quot;arr[&quot;+i+&quot;][&quot;+j+&quot;]=&quot;+arr[i][j]); } } } } G. 数组的排序 冒泡排序 选择排序 插入排序 快速排序 希尔排序 归并排序 折中排序 桶排序 选择排序 public class ArraySelectorSortDemo { public static void main(String[] args) { int [] arr = new int []{21,3,41,45,67,2,78,6}; //选择排序 for (int i = 0; i &lt; arr.length-1; i++) { for (int j = i+1; j &lt; arr.length; j++) { if(arr[i] &gt; arr[j]){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } for (int i : arr) { System.out.println(i); } } } 冒泡排序 public class ArrayBubbleSortDemo { public static void main(String[] args) { //冒泡排序：数组间两两之间进行比较，按照排序规则进行换位 int [] arr = new int []{21,3,41,45,67,2,78,6}; for (int i = 0; i &lt; arr.length; i++) { for (int j = 0; j &lt; arr.length - i - 1; j++) { if(arr[j] &gt; arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } for (int i : arr) { System.out.println(i); } } } 插入排序 public static void main(String[] args) { int [] arr = new int [] {21,3,41,45,67,2,78,6}; for(int i = 0;i &lt; arr.length-1;i++){ //当前准备比较的值 int temp = arr[i+1]; int num = i; //当前位置的值和前面所有的值比较进行换位 while(num &gt;= 0 &amp;&amp; temp &lt; arr[num] ){ arr[num + 1] = arr[num ]; num --; } arr[num + 1] = temp; } for (int k : arr) { System.out.println(k); } } public class ArrayInsertSortDemo { public static void main(String[] args) { int [] arr = new int []{21,3,41,45,67,2,78,6}; int temp; int i; int j; for(i = 1;i &lt; arr.length;i++){ for (j = i;j &gt; 0;j--){ if(arr[j] &lt; arr[j-1]){ swap(arr,j,j-1); } } } for (int k : arr) { System.out.println(k); } } public static void swap(int [] arr,int i,int j){ int temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } } 快速排序 H. 元素的查找 二分查找（折半查找） 要求数组本身有序，不能对数组排序后使用二分查找，因为会改变元素本身位置 public static int getIndex(int [] array,int value){ int left = 0; int right = array.length - 1; while(left &lt;= right){ int mid = (left + right) / 2; if (array[mid] &gt;= key) { right = mid - 1; }else { left = mid + 1; } } if(left &lt; arrat.length-1 &amp;&amp; array[left] == key){ return left; } return -1; } 基本查找（数组元素无序） 14. java的内存模型 java内存模型(Java Memory Model，JMM)是java虚拟机规范定义的，用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异，这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。可以避免像c++等直接使用物理硬件和操作系统的内存模型在不同操作系统和硬件平台下表现不同，比如有些c/c++程序可能在windows平台运行正常，而在linux平台却运行有问题。 JDK8之前的JVM内存布局 JDK8以后的JVM内存布局 栈内存是先进后出 栈内存只能保存基本数据类型的变量和值 栈内存保存对象的变量，引用到堆内存中的地址值 只要使用了new关键字就会在堆内存中开辟空间，new使用一次开辟一次 本地方法区 栈内存：int num = 10；基本数据类型声明变量 堆内存：new 引用数据类型 栈内存对象的变量引用堆内存的十六进制地址值 15. 方法 A. 理解：对一段代码的封装 访问修饰符 修饰符 返回值类型 方法名（形参列表）{ ​ 方法体； } public static void main（String [] args）{ ​ System.out.println(&quot;Hello&quot;)； } 方法的分类： 返回值 有返回值： 无返回值： 参数 有参数 无参数 访问权限修饰符： 作用：用来修饰成员变量和成员方法，从而实现对方法和变量的使用权限控制 分类： public：公开的 private：私有的 protected：受保护的 16. 变量作用域 定义变量的位置不同 全局变量：就是类中的所有方法都可以使用，当前类当前类不在使用的时候 由GC(垃圾回收器)回收 全部变量一般定义在类 开始，属性定义的位置 局部变量：只能在当前的方法中使用 方法执行完毕 变量从内存中释放 17. 代码块 在java中，用{}括起来的代码就被称为代码块 局部代码块 构造代码块 静态代码块 同步代码块 1. 局部代码块 在方法中出现，限定变量的作用范围（生命周期），让变量尽早释放，提高内存使用率 public class CodeBlockDemo{ public static void main(String [] args){ //局部代码块 { int a = 5; System.out.println(a); } //System.out.println(a);//无法使用a变量 } } 2. 构造代码块 在类中方法外出现，多个构造方法中相同的代码存放到一起，每次调用构造方法都会执行，而且在构造方法执行时一同执行，即每次创建对象都执行 public class CodeBlockDemo{ private int a; private String s; //构造代码块 { System.out.println(&quot;构造代码块&quot;); } //构造方法 public CodeBlockDemo(){ System.out.println(&quot;无参构造方法&quot;); } public CodeBlockDemo(int a,String s){ this.a = a; this.s = s; } } 3. 静态代码块 在类中方法外出现，被static修饰，用来给类进行初始化，在类被加载时执行，只执行一次 public class CodeBlockDemo{ private int a; private String s; //静态代码块 static{ System.out.println(&quot;静态代码块&quot;); } //构造方法 public CodeBlockDemo(){ System.out.println(&quot;无参构造方法&quot;); } public CodeBlockDemo(int a,String s){ this.a = a; this.s = s; } } 4. 同步代码块 Synchronize{}：解决线程安全问题 5. 静态代码块、构造代码块，构造方法的执行顺序和次数 静态代码块 ：在类被加载进内存时执行，只执行一次 构造代码块：在构造方法被调用之前执行，每次调用都会被执行 构造方法：对象被创建时执行，每次调用都会被执行 18. 方法的重载 方法的重载 参数的数据类型不同 参数的顺序不同 参数的个数不同 与方法的返回值无关 @OverLoad，早期方法重载声明，在jdk1.7以后更新不需要写了 public static int sum(int i , int j){ return i+j; } public static int sum(String i,int j){ return i+j; } @OverLoad public static int sum(String i ,int j,int k){ return i+j+k; } public static int sum(int j,int k,String i ){ return i+j+k; } public static void main (String [] args){ int c = num(1,1); System.out.println(c); } public static int num (int a,int b ){ int c = a+b; return c; } 19. 面向对象 面向对象是基于面向过程的 1. 封装（Encapsulation） 概述：封装(encapsulation)又叫隐藏实现(Hiding the implementation)，就是只公开代码单元的对外接口，而隐藏其具体实现。 详述： 将类的某些信息隐藏在类的内部，不允许外部程序进行直接的访问调用。 通过该类提供的方法来实现对隐藏信息的操作和访问。 隐藏对象的信息。 留出访问的对外接口。 特点： 提高了代码的安全性、复用性。 对成员变量实行更准确的控制。 封装可以隐藏内部程序实现的细节。 良好的封装能够减少代码之间的耦合度。 外部成员无法修改已封装好的程序代码。 方便数据检查，有利于保护对象信息的完整性，同时也提高程序的安全性。 便于修改，提高代码的可维护性。 封装原则： 把不需要对外提供的内容隐藏起来，提供对应的公共访问方式 封装性在Java中的体现 方法就是一种封装 关键字private也是一种封装 2. 继承（Inheritance） 概述：继承，Inheritance，是一种看起来很神奇的代码重用方式。 继承就是子类继承父类的特征和行为，使得子类对象（实例）具有父类的实例域和方法，或子类从父类继承方法，使得子类具有父类相同的行为。当然，如果在父类中拥有私有属性(private修饰)，则子类是不能被继承的。 特点： 提高代码复用性。 提高了代码的维护性 类与类之间产生联系（多态构成的前提） 类的耦合度增加了 父类的属性方法可以用于子类。 可以轻松的定义子类。 使设计应用程序变得简单 Java只能单继承，不能多继承，一个类只能有一个父类，不能有多个父类，但是可以多层继承 注意事项： 不要为了部分功能而用继承 子类只能继承父类所有非私有的成员（成员变量和成员方法） 子类不能继承父类的构造方法，但是可以通过super关键字访问父类构造方法 什么时候用使用继承： 继承体现的是一种关系：A is B（例如：是水果，是水果） 3. 多态（Polymorphism） 父类引用指向子类对象 开发原则：低耦合，高内聚 20. 类 一类相同事物的抽象 类的组成 成员变量：事物的属性（在类中方法外定义） 成员方法：事物的功能 构造方法：初始化对象（无参，有参，可被重载） 类的定义 class Student{ public int sid; public String sname; public Student(){} public Student(int id,String name){ this.sid = id; this.name = sname; } public void study(){ System.out.println(&quot;学习&quot;); } public void eat(){ System.out.println(&quot;吃饭&quot;); } } 21. 对象 某个事物的抽象 创建对象的格式： 类名 对象名 = new 类名/构造方法(参数列表); Student stu = new Studnet(); 为成员变量赋值 stu.sid = 174350; stu.sname = &quot;zhiyuan&quot;; 注：堆内存中的变量（引用类型）会被初始化，栈内存中的变量（基本类型）未初始化不可使用 22. 成员变量和局部变量的区别 在类中的位置不同 成员变量：类中方法外 局部变量：方法声明上或方法内 在内存中的位置不同 成员变量：在堆中 局部变量：在栈中 生命周期不同 成员变量：随着对象的的存在而存在，随着对象的消失而消失 局部变量：随着方法的调用而存在，随着方法的调用完成而消失 初始化不同 成员变量：有默认值 局部变量：无默认值 注意事项：局部变量的名字可以和成员变量的名字一样，在方法中用的时候，采用的是就近原则 class Demo{ public int num = 5; public static void show(){ int num = 5; System.out.println(num); } public static void main(String [] args){ System.out.println(num); show(); } } 23. 参数问题 基本类型：形参的改变不影响实参 引用类型：形参的改变直接影响实参 24. 匿名对象 定义：没有名字的对象，是对象的一种简化表示方法 应用： 匿名调用方法（如果需要调用多次方法，不建议使用匿名对象调用，因为每调用一次都创建一个新对象） 作为实际参数传递 好处：匿名对象使用完成之后就是垃圾了，可以被回收。 new Student(); //匿名调用方法 new Student().study(); //匿名对象做参数 new StudentDemo().studyDemo(new Student()); 25. equals（）与 == == 基本类型：比较的是值是否相同 引用类型：比较的是地址值是否相同 equals 引用类型：Object默认比较的是地址值，子类重写后默认比较的是内容是否相同 26. this关键字 class Person{ private String name; public void setName(String name){ name = name; } } 分析： 这里实际上是把局部变量的值又赋给了局部变量 应该是把这个值赋值给成员变量 成员变量是随着对象的创建而存在 之前 “对象名.成员变量名” 可以访问成员变量 那么我们就需要找到一个可以表示 “当前对象” 的东西 而java提供了一个关键字 “this” 就可以表示 “当前对象” this -&gt; 当前对象(所在类的对象的“引用”) 当前方法被谁调用，this就表示谁 修改上述例子 class Person{ private String name; public void setName(String name){ this.name = name; } } 27. 构造方法 作用：初始化对象 public class Student{ private int sid; private String sname; //无参构造方法 public Student(){ } //有参构造方法 public Student(int sid,String sname){ this.sid = sid; this.sname = sname; } //构造方法可以被重载 public Student(int sid){ this.sid = sid; } public Student(String sname){ this.sname = sname; } //Setter方法 public void setId(int sid){ this.sid = sid; } public void setName(String sname){ this.sname = sname; } //Getter方法 public int getId(){ return sid; } public String getName(){ return sname; } //重写toString方法 @Override public String toString() { return &quot;Student{&quot; + &quot;sid=&quot; + sid + &quot;, sname='&quot; + sname + '\\'' + '}'; } } public class StudentTest{ public static void main(String [] args){ //创建对象 Student stu1 = new Student(); System.out.println(stu1); Student stu2 = new Student(18,&quot;zhiyuan&quot;); System.out.println(stu2); Student stu3 = new Student(18); System.out.println(stu3); Student stu4 = new Student(&quot;zhiyuan&quot;); System.out.println(stu4); } } /* 输出结果 Student{sid=0, sname='null'} Student{sid=18, sname='zhiyuan'} Student{sid=18, sname='null'} Student{sid=0, sname='zhiyuan'} */ 说明： 如果用户自己没写，系统会自动生成一个无参构造方法 如果用户写了构造方法，无论有参无参，系统都不会再自动生成无参构造方法 构造方法可以重载 28. 类的初始化过程 Student s = new Student();在内存中都干了哪些事？ //加载Student.class文件进内存 //在栈内存中给变量s分配内存空间 //在堆内存中给学生对象开辟空间 //对学生的成员变量进行隐式初始化(默认初始化) //对学生的成员变量进行显式初始化(类内赋值初始化) //对学生的成员变量进行构造初始化(构造方法初始化) 29. static关键字 可以修饰成员变量和成员方法 特点： 被类内所有对象共享（也是我们判断要不要用static的标准） 随着类的加载而加载 优先于对象存在 可以通过类名调用（本身可以通过对象调用，建议用类名调用，否则失去了用static修饰的意义 ） 静态方法只能访问静态的成员变量和静态成员方法（静态方法中不能用this） 静态的成员变量和成员方法是对着类加载而加载的，this是随着对象的创建而存在的 public class Demo{ private int num; private static int num2 = 4; public void show(){ System.out.println(num); System.out.println(num2);//无法通过非静态的成员方法访问静态的成员变量 show2(); show3(); } public static void show1(){ System.out.println(num);//报错，无法通过静态的成员方法访问非静态的成员变量 System.out.println(num2); show2(); //静态的成员方法无法访问非静态的成员方法 show3(); } public void show2(){ } public static void show3(){ } } 非静态方法既可以访问静态的成员变量和成员方法，又可以访问非静态的成员变量和成员方法 public static void main(String [] args){ for(int i = 0;i &lt; args.length;i++){ System.out.println(args[i]); } } //String [] ars用于接收来自控制台交互的参数 静态的成员变量和成员变量 所属不同 成员变量：属于对象 静态成员变量：属于类 内存区域不同 成员变量：堆内存 静态变量：方法区中的静态区 出现时间不同 成员变量：随着对象的创建而存在，随着对象的消失而消失 静态变量：随着类的加载而加载，随着类的消失而消失 调用方式不同 成员变量：只能被对象调用 静态变量：可以使用类名调用，也可以使用对象名调用 30. main方法 public static void main(String [] args){ //public：权限修饰符，公共的 //static：被jvm调用，不需要创建对象 //void：返回值为空类型，即无返回值 //main：程序的主入口名，固定默认为main //String [] args：早期用于接收键盘录入数据，1.5版本后使用Scanner } //控制台编译运行时传入参数 javac Demo.java java Demo hello world java //尝试输出参数 public static void main(String [] args){ for(String s : args){ System.out.println(s); } } 31. 文档注释 可以使用javadoc命令生成网页形式的说明书 制作帮助文档（了解） package com.demo.javase; /** * @Author 絷缘 * @Date 2020/11/2 10:02 * @Version 1.0 **/ public class ArrayTools { /** * 打印数组 * @param arr 传入指定数组 */ public static void printArray(int [] arr){ for (int a:arr) { System.out.println(a); } } /** * 获得数组中的最大值 * @param arr 传入指定数组 * @return int 返回数组中的最大值 */ public static int getMax(int [] arr){ int max = arr[0]; for(int i = 0;i &lt; arr.length;i++){ if(arr[i] &gt; max){ max = arr[i]; } } return max; } /** * 获取数组中的最小值 * @param arr 传入指定数组 * @return int 返回数组中的最小值 */ public static int getMin(int [] arr){ int min = arr[0]; for(int i = 0;i &lt; arr.length;i++){ if(arr[i] &lt; min){ min = arr[i]; } } return min; } /** * 反转数组方法A * 逆序数组 * @param arr 传入指定数组 */ public static void reverseA(int [] arr){ for(int i = 0,j = arr.length - 1;i &lt; j;i++,j--){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } /** * 反转数组方法B * 逆序数组 * @param arr 传入指定数组 */ public static void reverseB(int [] arr){ for(int i = 0;i &lt; arr.length/2;i++){ int temp = arr[i]; arr[i] = arr[arr.length - 1 - i]; arr[arr.length - 1] = arr[i]; } } /** * 获取指定值第一次出现的索引值 * @param arr 传入的指定数组 * @param value 传入的指定值 * @return int 返回的是第一次出现的索引值，如果不存在返回-1 */ public static int getIndex(int [] arr,int value){ int index = -1; for(int i = 0;i &lt; arr.length;i++){ if(arr[i] == value){ index = i; break; } } return index; } /** * 获取指定索引处的值 * @param arr 传入指定数组 * @param index 传入指定索引 */ public static void getValue(int [] arr,int index){ if(index &gt; arr.length -1 || index &lt; 0){ for(int i = 0;i &lt; arr.length;i++){ if(index == i){ System.out.println(arr[i]); break; } } }else{ System.out.println(&quot;index错误&quot;); } } } 使用javadoc命令解析 格式： javadoc -d . -author -version ArrayTools.java 报错：找不到可以文档化的公共或受保护的类 原因：权限不够 解决方法：给类加上public 看文档 看类的结构 看包名 看类的说明 看类的结构 成员变量：字段摘要 构造方法：构造方法摘要 成员方法：方法摘要 左边：返回值，是否为静态 右边：方法名 外部调用（参考JavaDoc和字节码文件使用功能） 构造方法私有化 32. 继承（extends） Person类 package com.demo.javase; /** * @author 絷缘 * @version 1.0 * @date 2020/11/3 9:16 **/ public class Person { protected String name; protected int age; protected String gender; /** * 有参构造方法 * @param name 提供人的姓名 * @param age 提供人的年龄 * @param gender 提供人的性别 */ public Person(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } /** * 获取人的名字 * @return String 返回人的姓名 */ public String getName() { return name; } /** * 获取人的年龄 * @return int 返回人的年龄 */ public int getAge() { return age; } /** * 获取人的性别 * @return String 返回人的性别 */ public String getGender() { return gender; } /** * 设置人的姓名 * @param name 提供姓名参数 */ public void setName(String name) { this.name = name; } /** * 设置人的年龄 * @param age 提供年龄参数 */ public void setAge(int age) { this.age = age; } /** * 设置人的性别 * @param gender 提供性别参数 */ public void setGender(String gender) { this.gender = gender; } public void eat(){ System.out.println(&quot;吃饭&quot;); } public void sleep(){ System.out.println(&quot;睡觉&quot;); } public void work(){ System.out.println(&quot;工作&quot;); } public void study(){ System.out.println(&quot;学习&quot;); } public void play(){ System.out.println(&quot;玩耍&quot;); } @Override public String toString() { return &quot;Person{&quot; + &quot;name='&quot; + name + '\\'' + &quot;, age=&quot; + age + &quot;, gender='&quot; + gender + '\\'' + '}'; } } Student类 package com.demo.javase; /** * @author 絷缘 * @version 1.0 * @date 2020/11/3 9:26 **/ public class Student extends Person{ private int sid; public Student(int sid,String name,int age,String gender){ super(name,age,gender); this.sid = sid; } /** * 获取学生ID * @return int 返回学生的ID */ public int getSid() { return sid; } /** * 设置学生的ID * @param sid 提供学生ID参数 */ public void setSid(int sid) { this.sid = sid; } @Override public String toString() { return &quot;Student{&quot; + &quot;sid=&quot; + sid + &quot;, name='&quot; + name + '\\'' + &quot;, age=&quot; + age + &quot;, gender='&quot; + gender + '\\'' + '}'; } } 测试类 package com.demo.javase; /** * @author 絷缘 * @version 1.0 * @date 2020/11/3 9:28 **/ public class Test { public static void main(String[] args) { System.out.println(&quot;==========================================================&quot;); Student stu1 = new Student(174350,&quot;zhiyuan&quot;,18,&quot;male&quot;); System.out.println(stu1); System.out.println(&quot;==========================================================&quot;); System.out.println(&quot;sid=&quot;+stu1.getSid()); System.out.println(&quot;name=&quot;+stu1.getName()); System.out.println(&quot;age=&quot;+stu1.getAge()); System.out.println(&quot;gender=&quot;+stu1.getGender()); System.out.println(&quot;==========================================================&quot;); stu1.eat(); stu1.sleep(); stu1.study(); stu1.play(); System.out.println(&quot;==========================================================&quot;); } } 继承中的成员变量关系 类的组成 成员变量 构造方法 成员方法 子类的成员变量名字和父类的成员变量名一样的话 在子类的方法中找，有就用 在子类的成员位置找，有就用 在父类的成员位置找，有就用 如果找不到，就去父类的父类成员位置找，有就用 33. super关键字 super的用法和this很像 this表示本类对象的调用 super表示的是父类对象的调用 用法： 访问成员变量 this.成员变量 super.成员变量 访问构造方法 this() super() 访问成员方法 this.成员方法() super.成员方法() super和this的区别 this表示本类对象的调用 super表示的是父类对象的调用 注意：super调用构造方法或this调用构造方法时，必须是第一条语句 34. 继承中构造方法的关系 子类中所有构造方法默认都会访问父类中的无参构造方法 因为子类继承父类，会继承父类中的数据，还要使用这些数据，父类中的数据随着父类对象的存在才会存在，所以，子类初始化之前，一定会先初始化父类。 public class 子类 extends 父类{ public 子类构造(){ //super父类构造();不写，默认会调用父类构造方法初始化对象 } public 子类构造(参数列表){ super(参数列表); this.子类成员变量 = 参数; } } 35. 继承中成员方法的关系 子类中的成员方法与父类中的成员方法同名 使用子类对象调用方法，先看子类有没有，有就用，没有就看父类有没有，有就用，没有就报错 36. 方法的重写 在子父类中，子类出现了和父类中一模一样的方法声明，这种现象叫做方法重写（复写） 方法重写应用 当子类需要父类的功能，而功能的主体子类又有自己特有的内容时，可以重写父类继承过来的方法 这样既继承了父类的功能，又定义了子类的特有功能内容 注意事项： 父类中私有的方法不能被重写（因为父类中的私有方法无法被继承，更不要说重写了） 子类重写父类的方法，访问权限不能更低 父类的静态方法，子类也必须使用静态方法重写（本质不算方法重写，现象确实如此，涉及多态） 子类重写方法时，声明要一模一样 37. 方法重载和方法重写的区别 方法重载：同一个类中，出现多个方法名相同，参数列表不同的方法，这种现象称为方法重载 方法重写：在子类中，出现和父类中成员方法声明相同，功能主体不同的方法，这种现象称为方法重写 38. 访问权限修饰符 访问权限修饰符 描述 本类 同一个包的无关类 其他包的无关类 同一个包的子孙类 其他包的子孙类 public 访问权限公开 ✔️ ✔️ ✔️ ✔️ ✔️ protected 访问权限受保护 ✔️ ✔️ ❌ ✔️ ✔️ private 访问权限私有 ✔️ ❌ ❌ ❌ ❌ 默认的缺省的 friendly(同一个包内友好) ✔️ ✔️ ❌ ✔️ ❌ 1. public public 公开的，是访问权限限制最宽的修饰符。被public修饰的类、属性、及方法不仅可以跨类访问，而且可以跨包访问 2. protected protected 受保护的， 是介于public和private之间的一种访问修饰。被protected修饰的属性及方法只能被类本身的方法和子类访问。（子类在不同的包中也可以访问） 3. private private 私有的，对访问权限限制最窄的修饰符。被private修饰的属性以及方法只能被该类的对象访问。它的子类也不可以访问，更不支持跨包访问 4. friendly friendly 友好的，默认不加任何访问修饰符，只支持在同一个包中进行访问，对同一个包内的类友好 39. 其他修饰符 1. static 修饰变量：静态变量 独立于对象的变量，又称类变量，在类被加载进内存时加载，类无论被实例化几次，静态变量只有一份拷贝 修饰方法：静态方法 独立于对象的方法，又称类方法，在类被加载进内存时加载 静态方法只能访问静态成员（变量、方法） 2. final 我们只希望子类能够继承父类的方法，不希望他改动，java就提供了final关键字 修饰变量：变量一旦赋值后，不能被重新赋值。 被 final 修饰的实例变量必须指定初始值（显式初始化，构造初始化，构造代码块）。 final 修饰符通常和 static 修饰符一起使用来创建类常量。 static final double PI = 3.14; 修饰方法：父类方法可以被子类继承，但不可被重写 public class Person{ private String name; public final void changeName(String name){ this.name = name; } } 修饰类：表示最终类，该类不可被继承（但最终类可以继承别的类） public final class Person{ } final修饰局部变量 基本类型：值不能发生改变 引用类型：地址值不能发生改变，但是该对象在堆内存中的值是可以改变的 修饰形参时，在方法内部不可对值进行改变 final修饰成员变量的初始化时机：在对象构造完成之前赋值即可，只能赋值一次 3. abstract 修饰类：抽象类 修饰方法：抽象方法 4. synchronized 5. transient 6. volatile 40. 多态 同一事物在不同时刻表现出来不同的状态 例：水在低温下是固态，常温下是液态，高温下是气态 例：你在你爹面前是儿子，在你爷爷面前是孙子 1. 多态的前提和表现 前提： 有继承关系 有方法重写 表现： 父类引用指向子类对象（向上转型） 2. 多态中成员的访问特点 class Father{ int num = 111; public void show(){ System.out.println(&quot;show father&quot;); } public static void function(){ System.out.println(&quot;function father&quot;); } } class Son extends Father{ int num = 222; int num2 = 333; public void show(){ System.out.println(&quot;show son&quot;); } public static void function(){ System.out.println(&quot;function son&quot;); } public void method(){ System.out.println(&quot;method son&quot;); } } class Demo1{ public static void main(String [] args){ //多态 Father f1 = new Son(); System.out.println(f.num); System.out.println(f.num2);//找不到num2 } } 访问成员变量 编译看左边，运行看左边 访问成员方法 编译看左边，运行看右边（成员方法存在方法重写，所以运行看右边） 访问静态方法 编译看左边，运行看左边（静态和类相关，算不上重写，所以访问是父类） 访问构造方法 创建子类对象时，自动访问父类的构造方法，将父类数据进行初始化 3. 多态的好处和弊端 好处： 提高了程序的复用性、可维护性（由继承保证） 提高了程序的可扩展性（有多态的保证） 弊端： 无法访问到子类独有的内容（变量以及方法） 难道真的不能访问子类的内容吗？ 向下转型 Father f = new Son(); Son s = (Son)f; 4. 多态的作用 当把不同的子类对象都当做父类类型来看待，可以屏蔽不同子类对象之间的实现差异，从而写出通用的代码达到通用编程，以适应需求的不断变化。 41. 抽象类（abstract） 1. 抽象类的定义： 比如图形(Shape)类, 就是一个抽象的概念,因为我们无法计算这个**“图形”的面积,所以它的成员函数area()**是空的。 而继承它的子类**(矩形,椭圆形,三角形等)就可以去覆写area()**成员函数. 里面通过数学公式,计算出面积. 抽象类，用来表示一个抽象概念，不是一个具体的事物。 是一种只能定义类型,而不能产生对象的类，所以定义了抽象类则必须有子类的出现. 抽象类的好处在于能够明确地定义子类需要覆写的方法 抽象类需要使用abstract声明. 定义格式：public abstract class 类名 { } 2. 抽象方法 一个没有方法体的方法 有抽象方法的类就必须定义为抽象类 定义格式：public abstract 返回值类型 方法名（）； 3. 注意事项 抽象类不能被实例化(初学者很容易犯的错)，如果被实例化，就会报错，编译无法通过。只有抽象类的非抽象子类可以创建对象。 抽象类中不一定包含抽象方法，但是有抽象方法的类必定是抽象类。 抽象类中的抽象方法只是声明，不包含方法体，就是不给出方法的具体实现也就是方法的具体功能。 public abstract double computePay(); 构造方法，类方法（用 static 修饰的方法）不能声明为抽象方法。 如果子类是具体类，子类必须给出抽象类中的抽象方法的具体实 如果子类也是抽象类，子类可以不重写父类的抽象方法 4. 抽象类的构造方法的意义 用于子类访问父类初始化数据 可以通过多态的方式实现实例化 5. 抽象类中的成员特点 成员变量 可以是变量（成员变量，static 类变量） 也可以是常量（final 成员常量，static final 类常量） 成员方法 可以有抽象方法（要求子类必须干的事情） 也可以有非抽象方法（给子类继承，提高代码复用性） 构造方法 有构造，但是不能实例化 public abstract class Animal{ private String name; private int age; private String color; public Animal(){} public Animal(String name,int age,String color){ this.name = name; this.age = age; this.color = color; } public String getName(){ return name; } public int getAge(){ return age; } public String getColor(){ return color; } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public void setColor(String color){ this.color = color; } public abstract void eat(); public abstract void sleep(); public abstract void play(); } public class Dog extends Animal{ public Dog(){} public Dog(String name,int age,String color){ super(name,age,color); } public void eat(){ System.out.println(&quot;狗吃狗&quot;); } public void sleep(){ System.out.println(&quot;狗趴着睡&quot;); } public void play(){ System.out.println(&quot;狗玩球&quot;); } public void lookDoor(){ System.out.println(&quot;狗看门&quot;); } } public class Cat extends Animal{ public Cat(){} public Cat(String name,int age,String color){ super(name,age,color); } public void eat(){ System.out.println(&quot;猫吃猫&quot;); } public void sleep(){ System.out.println(&quot;猫蜷着睡&quot;); } public void play(){ System.out.println(&quot;猫玩毛线&quot;); } public void catchMouse(){ System.out.println(&quot;猫抓老鼠&quot;); } } public class Demo{ public static void main(String [] args){ Cat c1 = new Cat(); c1.setName(&quot;咪咪&quot;); c1.setAge(1); c1.setColor(&quot;奶牛色&quot;); System.out.println(c1.getName()+c1.getAge()+c1.getColor()); c1.eat(); c1.sleep(); c1.play(); c1.catchMouse(); Cat c2 = new Cat(&quot;橘猫&quot;,2,&quot;橘黄色&quot;); System.out.println(c2.getName()+c2.getAge()+c2.getColor()); c2.eat(); c2.sleep(); c2.play(); c2.catchMouse(); //多态 Animal a1 = new Cat(); a1.setName(&quot;狸花猫&quot;); a1.setAge(2); a12.setColor(&quot;花&quot;); System.out.println(a1.getName()+a1.getAge()+a1.getColor()); a1.eat(); a1.sleep(); a1.play(); Cat c3 = (Cat)a1; c3.catchMouse(); Animal a2 = new Cat(&quot;蓝猫&quot;,1,&quot;蓝色&quot;); System.out.println(a2.getName()+a2.getAge()+a2.getColor()); a2.eat(); a2.sleep(); a2.play(); Cat c4 = (Cat)a2; c4.catchMouse(); } } 6. 抽象类中的几个问题 一个类如果没有抽象方法，能不能定义成抽象类？如果可以，有什么意义？ 可以，意义可以让类无法被实例化，即无法创建对象 abstract类和abstract方法不能被以下修饰： private abstract void show（） final abstract void show（） static abstract void show（） 42. 接口 1. 概述 为了体现事物功能的扩展性，java就提供了接口来定义额外的功能，不给具体实现。 接口只描述所应该具备的方法，并没有具体实现，具体的实现由接口的实现类(相当于接口的子类)来完成。这样将功能的定义与实现分离，优化了程序设计。 请记住：一切事物均有功能，即一切事物均有接口。 2. 接口的表示形式 关键字：interface 格式：interface 接口名 { } 实现接口关键字：implements 格式：class 类名 implements 接口名{ } 3. 特点 接口不能被实例化 可以依赖具体的实现类实现实例化（接口多态） 4. 接口的实现类 可以是抽象类，意义不大 可以是具体类，一定要重写接口中所有的抽象方法 5. 多态类型 具体类多态（几乎不用） 抽象类多态（常用） 接口类多态（常用） interface AnimalInter { //钻火圈的额外功能 public abstract void 钻火圈(); } class Tiger implements AnimalInter{ public void 钻火圈(){ System.out.pritln(&quot;老虎钻火圈&quot;); } } class Demo{ public static void main(String [] args){ Tiger t = new Tiger(); t.钻火圈(); AnimalInter a = new Tiger(); a.钻火圈(); } } 6. 接口中的成员特点 成员变量 默认被 public static final 修饰，只能是类常量 自己写上，否则会误认为是变量 构造方法 没有构造方法，也不能有构造方法 因为接口是扩展功能，没有具体的实体对象 那么接口如何实现多态？ java中所有的类都直接或者间接继承了一个类，这个类就是Object类 Object是java中所有类的基类，是类层次结构中的根类 成员方法 接口中所有的方法都被public abstract修饰，即接口中所有的方法都是抽象方法 （其实可以出现非抽象方法，但不要记忆default void show(){}） 默认被 public abstract 修饰 interface Inter{ public static final int num = 5; public static final int num2 = 6; public static final int num3 = 7; public static final int num4 = 8; public abstract void show(); } class InterImp implements Inter{ } class Demo{ public static void main(String [] args){ } } 7. 类和类、类和接口、接口和接口之间的关系 类和类 继承关系，只能单继承，可以多层继承 类和接口 实现关系，可以单实现，也可以多实现 还可以在继承一个类的基础上同时实现多个接口 接口和接口 继承关系，可以单继承，也可以多继承 8. 抽象类和接口的区别 成员区别： 抽象类 成员变量：可以是变量，可以是常量 构造方法：有 成员方法：可以有抽象方法，也可以有非抽象方法 接口 成员变量：都是静态常量 构造方法：没有 成员方法：只能是抽象方法 关系区别： 类和类：继承，单继承 类和接口：实现，多实现 接口和接口：继承，多继承 设计区别： 抽象类：存储继承体系中共性的内容，体现的是 is a 的关系 接口：体现事物的扩展功能，体现的是 like a 的关系 抽象类中可以有非抽象方法，接口中默认都为抽象方法（public abstract） 抽象类中的成员变量可以随便定义，接口中的成员变量都是类常量（public static final） 9. 猫狗案例，加入一个钻火圈的额外功能 interface AnimalInter{ //定义额外功能 public abstract void 钻火圈(){ } } public abstract class Animal{ private String name; private int age; private String color; public Animal(){ } public Animal(String name,int age,String color){ this.name = name; this.age = age; this.color = color; } public String getName(){ return name; } public int getAge(){ return age; } public String getColor(){ return color; } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public void setColor(String color){ this.color = color; } public abstract void eat(); public abstract void sleep(); public abstract void play(); public void show(){ System.out.println(&quot;Animal{&quot;+ name + &quot;,&quot; + age + &quot;,&quot; + color + &quot;}&quot;); } } //狗也是动物，所以继承动物 public class Dog extends Animal{ public Dog(){} public Dog(String name,int age,String color){ super(name,age,color); } public void eat(){ System.out.println(&quot;狗吃骨头&quot;); } public void sleep(){ System.out.println(&quot;狗趴着睡&quot;); } public void play(){ System.out.println(&quot;狗玩球&quot;); } public void lookDoor(){ System.out.println(&quot;狗会看门&quot;); } } //猫也是动物，所以继承动物 public class Cat extends Animal{ public Cat(){} public Cat(String name,int age,String color){ super(name,age,color); } public void eat(){ System.out.println(&quot;猫吃鱼&quot;); } public void sleep(){ System.out.println(&quot;猫蜷着睡&quot;); } public void play(){ System.out.println(&quot;猫玩毛线&quot;); } public void catchMouse(){ System.out.println(&quot;猫会抓老鼠&quot;); } } //屌狗也是狗，所以继承狗 public class DiaoDog extends Dog implements AnimalInter{ public DiaoDog(){} public DiaoDog(String name,int age,String color){ super(name,age,color); } public void zhq(){ System.out.println(&quot;屌狗钻火圈&quot;); } } //屌猫也是猫，所以继承猫 public class DiaoCat extends Cat implements AnimalInter{ public DiaoCat(){} public DiaoCat(String name,int age,String color){ super(name,age,color); } public void zhq(){ System.out.println(&quot;屌猫钻火圈&quot;); } } //测试类 public class Demo{ public static void main(String[]args){ //测试普通狗 Dog d1 = new Dog(&quot;哈巴狗&quot;,2,&quot;黄色&quot;); d1.show(); d1.eat(); d1.sleep(); d1.play(); d1.lookDoor(); //测试屌狗 DiaoDog d2 = new DiaoDog(&quot;金毛&quot;,3,&quot;金色&quot;); d2.show(); d2.eat (); d2.sleep(); d2.play(); d2.lookDoor(); d2.zhq(); //测试普通猫 Cat c1 = new Cat(&quot;橘猫&quot;,2,&quot;橘黄色&quot;); c1.show(); c1.eat(); c1.sleep(); c1.play(); c1.catchMouse(); //测试屌猫 DiaoCat c2 = new DiaoCat(&quot;狸花猫&quot;,3,&quot;狸花&quot;); c2.show(); c2.eat (); c2.sleep(); c2.play(); c2.catchMouse(); c2.zhq(); } } 43. 形参和返回值深入研究 1. 形参 基本类型 引用类型 类：要的是该类的对象 抽象类：要的是该抽象类的具体子类的对象 接口：要的是该接口的具体实现类的对象 2. 返回值类型 基本类型 引用类型 类：返回该类的具体对象 抽象类：返回该抽象类的具体子类对象 接口：返回该接口具体实现类对象 链式编程：new Student().study() 每次调用方法之后返回的是另一个对象，而你需要继续调用这个对象的方法，就可以直接.方法名 44. 包 1. 作用 把功能相似或相关的类或接口组织在同一个包中，方便类的查找和使用。 如同文件夹一样，包也采用了树形目录的存储方式。同一个包中的类名字是不同的，不同的包中的类的名字是可以相同的，当同时调用两个不同包中相同类名的类时，应该加上包名加以区别。因此，包可以避免名字冲突。 包也限定了访问权限，拥有包访问权限的类才能访问某个包中的类。 Java 使用包（package）这种机制是为了防止命名冲突，访问控制，提供搜索和定位类（class）、接口、枚举（enumerations）和注释（annotation） 区分同名文件，对类文件进行分类管理和存储 2. 包的划分 基本划分 按功能分 com.dnc.add com.dnc.delete com.dnc.update com.dnc.select 按模块分 com.dnc.teacher com.dnc.student 3. 包的定义和注意事项 格式： package 包名； 多级包用点隔开 注意： package语句必须为程序的第一条语句 一个java文件中只能有一条package语句，且写在第一行 4. 带包的编译和运行 手动写：编写一个带包的java文件，使用javac编译，手动创建包，将编译后的字节码文件放入包内，回到编写java文件的路径下，用java命令运行，要带包名 java com.dnc.demo.Demo 自动：编写一个带包的java文件，javac编译时带上包名，回到编写java文件的路径下，用java命令运行 javac -d . HelloWorld.java java com.dnc.demo.Demo 5. 导包 不同包下类之间的访问，每次使用的时候都要加包名，为了解决这个问题，java就提供了导包功能。 格式：import 包名； 注意： 用谁导谁，直接导入具体的类，写在package下 虽然包名后面可以写*，不建议 一个java文件中可以有多条import语句 45. 类和类的组成可用的修饰符 修饰符： 权限修饰符：private 默认 protected public 状态修饰符：static final 抽象修饰符：abstract 类： 权限修饰符：默认 public 状态修饰符：final 抽象修饰符：abstract 成员变量： 权限修饰符：private 默认 protected public 状态修饰符：static final 抽象修饰符：没有 成员方法： 权限修饰符：private 默认 protected public 状态修饰符：static final 抽象修饰符：abstract 构造方法： 权限修饰符：public private protected 默认 状态修饰符：没有 抽象修饰符：没有 除此之外的常用组合： 成员变量： public static final 成员方法： public static public abstract public final 46. 内部类 1. 把一个类定义在另一个类的内部，这个里面的类就是内部类 class A{ class B{} //B为成员内部类 public void method(){ class C{} //C为局部内部类 } } 2. 分类 2.1 根据内部类在类中定义的位置不同 成员内部类 局部内部类 2.2 根据状态修饰符 非静态内部类 静态内部类（static） 静态内部类无法访问外部类的成员 2.3 根据访问权限修饰符 私有内部类（private） 如果你不希望内部类被外部类访问可以使用 private 修饰符 受保护的内部类（protected） 默认的内部类（缺省） 公开的内部类（public） 3. 成员内部类 3.1 格式 class Outer{ class Inner{} } 3.2 创建对象格式 Outer.Inner i = new Outer().new Inner(); 3.3. 内部类的成员与外部类的成员的使用方式 内部类可以直接访问外部类的东西 外部类必须创建对象才能访问内部类的东西 内部类直接访问外部类成员可以使用以下格式 Outer.this.成员变量/成员方法() 例： public class Outer{ private int num = 10; public class Inner{ int num = 20; public void methodInner(){ int num = 30; System.out.println(num); //局部变量 System.out.println(this.num); //内部类的成员变量 System.out.println(Outer.this.num); //外部类的成员变量 this.show(); //内部类的成员方法 Outer.this.show(); //外部类的成员方法 } public void show(){ System.out.println(&quot;Inner show()&quot;); } } public void show(){ System.out.println(&quot;Outer show()&quot;); } } 3.4 成员内部类常见用法和常用修饰符 private：为了保证安全 特殊情况下还需要让别人访问，就提供一个公有的方法，让外界来间接访问 public class Outer{ private class Inner{ public void show(){ System.out.println(&quot;Inner show()&quot;); } } public void methos(){ Inner i = new Inner(); i.show(); } } static：为了让数据访问更方便（可以看成外部类的静态成员） 静态内部类可以有静态成员，非静态内部类不能有静态成员。 静态内部类的创建不依赖于外部类，非静态内部类必须依赖于外部类的创建而创建。 public class Outer{ private static int num = 2; public static class Inner{ public static void show(){ System.out.println(num); } } } 创建对象格式 Outer.Inner i = new Outer.Inner(); 4. 局部内部类 4.1 定义： 局部内部类是定义在一个方法或者一个作用域里面的类，它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。 4.2 格式： class Outer{ private int num = 5; public void show(){ int num = 6; //局部内部类 class Inner{ public void function(){ System.out.println(num); System.out.println(Outer.this.num); } } } } 4.3 注意事项： 局部内部类就像是方法里面的一个局部变量一样，是不能有 public、protected、private 以及 static 修饰符的。 局部内部类可以直接访问外部类成员，即使是私有的 局部内部类中访问局部变量，局部变量会被变成final变量，JDK8之后，默认被final修饰，JDK7之前，需要自己添加final修饰符 方法执行结束出栈，局部变量不存在了，但是方法中的局部内部类实例化的对象在堆中依然存在，还会使用局部变量的值，而此时局部变量并不存在，被对象访问的只是一个字面值常量，所以会被final修饰 其实这是规定，无需理解，是延长变量生命周期的一种方式，因为类是加载到方法区中。而局部变量是随意方法的进栈而产生，随着方法的弹栈而消失，所以在局部内部类访问局部变量时，要给局部变量给final修饰使其变成常量，进入常量池，生命周期变长。 在外部类中不能创建局部内部类的对象 外部类不能直接访问局部内部类，只能在方法体中间接访问局部内部类，且访问必须在内部类定义之后 5. 匿名内部类 5.1 格式： new 父类名或者接口名(){ //方法重写 } 5.2 本质 是一个继承了父类或者实现了接口的具体子类匿名对象 5.3 例 interface Inter{ public abstract void show(); } class Outer{ public abstract void method(){ //匿名内部类 new Inter(){ public void show(){ System.out.println(&quot;匿名内部类&quot;); } }.show(); } } public class Test{ public static void main(String [] args){ //接口多态 Inter i = new Inter(){ public void show(){ System.out.println(&quot;匿名内部类&quot;); } }; i.show(); } } 匿名内部类应该是平时我们编写代码时用得最多的，在编写事件监听的代码时使用匿名内部类不但方便，而且使代码更加容易维护，以下是一段 Android 事件监听代码 scan_bt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } }); history_bt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } }); 这段代码为两个按钮设置监听器，这里面就使用了匿名内部类 new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } } 5.4 注意事项 匿名内部类是唯一一种没有构造器的类。正因为其没有构造器，所以匿名内部类的使用范围非常有限，大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为 Outter$1.class。一般来说，匿名内部类用于继承其他类或是实现接口，并不需要增加额外的方法，只是对继承方法的实现或是重写。 匿名内部类可以访问外部所有成员，包括私有 匿名内部类访问局部变量时，变量必须要用final修饰，JDK8以后即使没有用final修饰，会自动补上 匿名内部类用于继承其他类或是实现接口，作为参数去给方法传值 5.5 例 public interface Inter{ public abstract void show(); } public class Outer{ public static Inter method(){ return new Inter(){ public void show(){ System.out.println(&quot;我爱**&quot;); } }; } } public class Test{ public static void main(String [] args){ Outer.method().show(); } } 6. Lambda表达式 6.1 概述：用来实现一个接口，本质是一个匿名函数 6.2 条件： 要求被实现接口中有且仅有一个需要被实现的方法（函数式接口） 6.3 函数式接口： interface A{ public abstract void show(); } //标准的函数式接口定义，有且仅有一个需要被实现类重写的方法 interface B{ public abstract void show(); default void display(){} } //这里为什么说B也是一个函数式接口，是因为在JDK1.8版本中，接口中可以有default方法，default关键字修饰的方法就是初始化的抽象方法。或者说是一个已经实现了的抽象方法，不需要再在其他implement接口位置进行实现。比如定义了一个接口，有大量的类实现了这个接口，但是新需求来了，需要在原有的基础上添加一个方法，而使用default关键字的话就不用每个实现类都实现一次，只要在顶层接口中用default实现此方法，所有实现类就会全部拥有该方法，所以说display（）已经被重写，所以类实现此接口时只需要重写show（）方法，满足函数式接口的定义 interface C{ public abstract void show(); public static void display(){} } //这里为什么C也是一个函数式接口，是因为在JDK1.8版本中，接口中可以有静态方法，虽然静态方法可以不用实现但是必须有方法体，即{}，在类实现此接口时，不会继承接口中的static的方法，也就重写不了，所以需要被重写的方法只有show（）一个，满足函数式接口的定义 interface D{ public abstract void show(); public String toString(); } //这里为什么说D也是一个函数式接口，是因为在类实现此接口时，由于默认预先继承Object类，而Object类重写了toString（）方法也算重写过了toString（）方法，因此此接口需要被实现的方法只有show（）一个，满足函数式接口的定义 6.4 @FunctionalInterface @FunctionalInterface 注解 表示接口是一个函数式接口 可以用来验证某个接口是否为函数式接口 47. 常用类(API) java.lang包下的类使用时无需导包，其他的都需要导包 1. Math类 所在包：java.lang.Math 常用方法： public static double random（）：返回一个大于等于0.0，小于1.0的double型数字 public static double abs（double a）：返回double类型的绝对值 public static float abs（float a）：返回float类型的绝对值 public static int abs（int a）：返回int类型的绝对值 public static long abs（long a）：返回long类型的绝对值 public static double asin（double a）：返回角度的反正弦值 public static double acos（double a）：返回角度的反余弦值 public static double atan（double a）：返回角度的反正切值 public static double sin（double a）：返回角度的正弦值 public static double cos（double a）：返回角度的余弦值 public static double tan（double a）：返回角度的正切值 public static double log（double a）：返回自然对数ln（以e为底）值 public static double pow（double a，double b）：返回a的b次方值 public static long round（double a）：四舍五入为long public static int round（float a）：四舍五入为int public static double ceil（double a）：向上取整为double public static double floor（double a）：向下取整double public static double sqrt（double a）：返回a的开平方根 2. Random类 所在包：jav.util.Random 构造方法： public Random（）：创建一个新的随机数生成器 public Random（long seed）：使用种子创建一个新的随机数生成器 常用方法： public int nextInt()：获取一个随机 int 数字（范围是int类型的范围，有正负两种） import java.util.Random; ... Random r = new Random(); int num = r.nextInt(); //-2147483648~2147483647 System.out.println(num); public int nextInt(int num)：获取一个随机 int 数字，（参数表示范围，左闭右开区间） import java.util.Random; ... Random r = new Random(); int num = r.nextInt(5);//[0,5) System.out.println(num); public void setSeed（long seed）：设置随机数种子（有规律可循的伪随机数） public static void main(String [] args){ System.out.println(&quot;未设置随机数种子，取到的随机数&quot;); for (int k = 0; k &lt; 5; k++) { Random ran = new Random(); for (int i = 0; i &lt; 10; i++) { System.out.print(ran.nextInt(100)+&quot;\\t&quot;); } System.out.println(&quot;&quot;); } System.out.println(&quot;设置随机数种子后，取到的随机数&quot;); for (int j = 0; j &lt; 5; j++) { Random ran2 = new Random(); ran2.setSeed(100); for (int i = 0; i &lt; 10; i++) { System.out.print(ran2.nextInt(100)+&quot;\\t&quot;); } System.out.println(&quot;&quot;); } } //输出结果 //未设置随机数种子，取到的随机数 58 76 62 95 90 61 53 30 31 20 87 22 11 77 87 61 9 76 43 81 17 52 16 94 10 14 11 11 73 83 11 93 34 66 82 87 30 43 65 81 12 31 77 91 80 54 58 16 86 46 //设置随机数种子后，取到的随机数 15 50 74 88 91 66 36 88 23 13 15 50 74 88 91 66 36 88 23 13 15 50 74 88 91 66 36 88 23 13 15 50 74 88 91 66 36 88 23 13 15 50 74 88 91 66 36 88 23 13 3. Object类 所在包：java.lang.Object 构造方法： public Object（） 如果一个类没有继承其他类，则去使用Object的无参构造方法 常用方法： protected Object clone（） 创建一个副本对象 自定义类实现Cloneable接口，是一个标记接口，实现了这个接口的类的对象可以实现自我克隆 自定义类中重写Object中的clone方法 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } boolean equals（Object obj） 比较两个对象是否相等 public boolean equals(Object obj) { return (this == obj); } 默认的equals方法比较对象引用地址，没有意义，子类应该重写这个方法 public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } protected void finalize（） 当对象不再使用的时候，提醒垃圾回收器来回收对象 class&lt;?&gt; getClass（） 返回当前正在运行的类的字节码文件对象 int hashCode（） 返回对象的哈希Code值 是通过对象地址值计算出来的，并不是对象的真正地址 String toString（） 返回对象的字符串表示形式 //Object类中的toString方法 public String toString() { return getClass().getName() + &quot;@&quot; + Integer.toHexString(hashCode()); } 默认的toString方法没有意义，子类应该去重写该方法 @Override public String toString() { return &quot;Student{&quot; + &quot;sname='&quot; + sname + '\\'' + &quot;, sage=&quot; + sage + &quot;, sgender='&quot; + sgender + '\\'' + '}'; } 4. Scanner类 接受键盘标准输入 构造方法 Scanner（InputStream in） InputStream in = System.in； 成员方法 nextXxxx（）：查找并返回下一个键盘输入数据 next（） nextBoolean（） nextByte（） nextDouble（） nextFloat（） nextInt（） nextLine（） nextLong（） nextShort（） hasNextXxxx（）：判断并返回是否存在下一个键盘输入数据 hasNext（） hasNextBoolean（） hasNextByte（） hasNextDouble（） hasNextFloat（） hasNextInt（） hasNextLine（） hasNextLong（） hasNextShort（） close（）：关闭Scanner对象 先获取一个数字，再获取一个字符串时，不让输入直接结束了，因为回车也算一个字符 可以把两个数据都以字符串形式接受，然后在进行转换 5.Arrays类 常用方法 sort方法： public static void sort（数组）：数组排序 toString方法： public static String toString（数组）：将数组转为字符串 binarySearch方法： public static int binarySearch（被查找的数组，被查找的值）：使用二分法在指定数组中查找指定值 public static List asList（T... a）：把数组转集合 数组转集合后，集合长度不可变 6. System类 包：java.lang.System 字段： static printStream err：标准错误输出流 static InputStream in：标准输入流 static PrintStream out：标准输出流 成员方法： public static long currentTimeMillis（）：获取当前时间（单位：毫秒） public static void exit（int status）：终止当前运行的Java虚拟机 status为状态码，按照惯例，非零状态码表示异常终止 public static void gc（）：运行垃圾回收器 public static Properties getProperties（）：获取当前的系统属性 public static void arraycopy（Object src，int srcPos，Object dest，int destPos，int length） src：原数组 srcPos：原数组中的起始位置 dest：目标数组 destPos：目标数组的起始位置 length：要复制的数组元素的数量 7. Collections类 包：java.util.Collections 常用方法： public static boolean addAll(Collection&lt;? super T&gt; c, T... elements)：将多个元素加入指定集合中 public static void shuffle（List&lt;?&gt; list）：打乱集合顺序 public static void sort（List list）：将集合按照默认顺序（升序）排列 public static void sort（List list，Comparator&lt;? super T&gt;）：将集合中元素按照指定比较器指定的排序规则排序 48. String类 包：java.lang.String 注意事项 字符串是常量，一旦被创建出来，不可改变 正是因为字符串是不可改变的，所以字符串可以共享使用 jdk1.8及以前String使用的是char数组，jdk1.9及以后使用的是byte数组。 因为开发人员发现人们使用的字符串值是拉丁字符居多而之前使用的char数组每一个char占用两个字节而拉丁字符只需要一个字节就可以存储，剩下的一个字节就浪费了，造成内存的浪费，gc的更加频繁。因此在jdk9中将String底层的实现改为了byte数组。 1. 构造方法 public String（）：创建一个空白字符串，不含有任何内容 public String（char[ ] chars）：根据字符数组的内容，来创建对应的字符串 public String（byte[ ] bytes）：根据字节数组的内容，来创建对应的字符串 public String（byte[] bytes, Charset charset）：按指定字符集解码并创建对应字符串 public String（byte[] bytes, int offset, int length）：按照字节数组内容，从指定位置开始指定长度创建对应字符串 public String（byte[] bytes, int offset, int length, Charset charset）：按照字节数组内容按指定编码从指定位置开始指定长度的字节数组内容创建对应字符串 public String（String original）：初始化新创建的String对象 public String（StringBuffer buffer）：将StringBuffer字符序列转为String对象 public String（StringBuilder builder）：将StringBuilder字符序列转为String对象 String str1 = new String(); char [] c = {'a','b','c'}; String str2 = new String(c); byte [] b = {65,66,67};//ASCII码：A，B，C String str3 = new String(b); //直接创建 String str4 = &quot;String&quot;; //直接写上双引号，即使没有new，也会创建对象 2. 字符串的常量池 JDK1.8之前，字符串常量池位于运行时常量池，运行时常量池存在于方法区中，JDK1.8之后，字符串常量池位于堆中 对于引用类型来说，==比较的是地址值 双引号直接写的字符串在常量池中，new的不在常量池中 Java6和6之前，常量池是存放在方法区（此时hotspot虚拟机对方法区的实现为永久代）中的。 Java7，将字符串常量池是单独存放到了堆中，运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代 Java8之后，取消了整个永久代区域，取而代之的是元空间。运行时常量池和静态常量池存放在元空间中，而字符串常量池依然存放在堆中，只不过方法区的实现从永久代变成了元空间(堆外内存) 在JDK1.8中，使用元空间代替永久代来实现方法区，但是方法区并没有改变，所谓&quot;Your father will always be your father&quot;，变动的只是方法区中内容的物理存放位置。正如上面所说，类型信息（元数据信息）等其他信息被移动到了元空间中；但是运行时常量池和字符串常量池被移动到了堆中。但是不论它们物理上如何存放，逻辑上还是属于方法区的。 JDK1.8中字符串常量池和运行时常量池逻辑上属于方法区，但是实际存放在堆内存中，因此既可以说两者存放在堆中，也可以说两则存在于方法区中，这就是造成误解的地方。 其实，移除永久代的工作从JDK1.7就开始了。JDK1.7中，存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中，并没完全移除，譬如符号引用(Symbols)转移到了native heap；字面量(interned strings)转移到了java heap；类的静态变量(class statics)转移到了java heap。 引用自：https://www.cnblogs.com/cosmos-wong/p/12925299.html 问题：String str1 = new String(&quot;abc&quot;)；创建了多少个对象？ 从字符串常量池中查找是否存在“abc”对象，如果存在，返回引用地址，如果不存在，则创建一个对象 new String（）在堆中开辟空间创建了一个对象 综上所述，创建了两个对象，一个引用 问题：String str1 = new String(&quot;A&quot;+&quot;B&quot;) ; 会创建多少个对象？ “A”如果没有在字符串常量池中被找到，就会被创建 “B”如果没有在字符串常量池中被找到，就会被创建 “AB”如果没有在字符串常量池中被找到，就会被创建 new String（）在堆中开辟空间创建了一个对象 综上所述，创建了四个对象，一个引用 问题：String str2 = new String(&quot;ABC&quot;) + &quot;ABC&quot; ; 会创建多少个对象? “ABC”如果没有在字符串常量池中被找到，就会被创建 “ABC”已经被创建，在字符串常量池中找到，返回引用地址 new String（）在堆中创建了一个对象 “ABCABC”被创建 综上所述，创建了三个对象，一个引用 3. 常用方法 public boolean equals（Object obj） 参数可以是任何对象，只有参数为一个字符串并且内容相同才会返回true，否则返回false 如果一个常量和一个变量比较 &quot;abc&quot;.equals(str) ✔️推荐使用，当str为null时，返回false，结果依然正确 str.equals(&quot;abc&quot;)​ ​ ❌不推荐使用，当str为null时，会出现NullPointerException空指针异常 public boolean equalsIgnoreCase(String str) 忽略大小写，直接比较内容 public char charAt（int index） 返回指定索引处的字符 public boolean endsWith（String str） 判断字符串是否以制定后缀结尾 public boolean startsWith（String str） 判断字符串是否以指定前缀开头 public boolean contains(CharSequence s) 当且仅当此字符串包含指定的char值序列时才返回true CharSequence是一个接口，String，StringBuilder，StringBuffer均是此接口的实现 public byte[ ] getBytes（） 使用默认字符集编码将字符串编码为字节数组 public byte[ ] getBytes（Charset charset） 使用给定编码将字符串编码为字节数组 public int indexOf（String str） 返回指定字符串在字符串内第一次出现的索引 public int indexOf（String str，int fromIndex） 返回指定字符串从指定索引处开始在字符串内第一次出现的索引 public int lastIndexOf（String str） 返回指定字符串从在字符串中最后一次出现的位置 public int lastIndexOf（String str, int fromIndex） 返回指定字符串从指定索引处开始在字符串内最后一次出现的索引 public boolean isEmpty（） 判断字符串是否为空 public String intern（） 返回字符串对象的规范表示 public int length（） 返回该字符串的长度 public String replace（char oldchar，char newchar） 提供被替换的字符，和替换的新字符，返回替换后的字符串结果 public String replaceAll（String regex，String replacement） 提供被替换字符串的正则表达式规则，和替换的字符串，返回替换后的结果 public String replaceFirst（String regex，String replacement） 提供被替换字符串的正则表达式规则，和替换的字符串，只替换第一个满足条件的字符串并返回结果 public String [ ] split（String regex） 提供字符串分割的正则表达式规则，将分割后的结果存入字符串数组并返回 public String [ ] split（String regex，int limit） 提供字符串分割的正则表达式规则，limit控制分割的次数，次数为limit-1，将分割后的结果存入字符串数组并返回 官方解释 limit 参数控制模式应用的次数，因此影响所得数组的长度。如果该限制 n 大于 0，则模式将被最多应用 n - 1 次，数组的长度将不会大于 n，而且数组的最后一项将包含所有超出最后匹配的定界符的输入。如果 n 为非正，那么模式将被应用尽可能多的次数，而且数组可以是任何长度。如果 n 为 0，那么模式将被应用尽可能多的次数，数组可以是任何长度，并且结尾空字符串将被丢弃。 public String substring（int beginIndex） 提供切割字符串的起始位置，返回切割后的结果，包头不包尾 public String substring（int beginIndex，int endIndex） 提供切割字符串的起始和结束位置，返回切割后的结果，包头不包尾 public char [ ] toCharArray（） 将字符串转为新的字符数组并返回字符数组 public String toString（） 将字符串转为字符串并返回 为什么字符串依然有转为字符串的方法，因为java中所有的类都有一个默认的父类Object，toString方法就来自Object public String toLowerCase（） 将字符串按照默认语言环境，转小写 public String toUpperCase（） 将字符串按照默认语言环境，转大写 public static String valueOf（数据类型 变量名） 将任意数据类型转为字符串形式 public String concat（String str） 将指定字符串连接到该字符串末尾 public int compareTo（String anotherString） 按字典顺序（ASCII码表Unicode码）比较两个字符串 一个一个的字符ASCII码依次比较，如果第一个大，直接返回第一个字符码值差值 如果等于返回0 49. StringBuffer类 1. 概述 我们对字符串进行拼接的时候，每次都会产生一个新的String对象，耗时又浪费时间，使用StringBuffer可以解决这个问题 线程安全，可变的字符序列 包：java.lang.StringBuffer 2. 构造方法 public StringBuffer（） 初始化空内容的字符串缓冲区 public StringBuffer（CharSequence seq） 初始化指定字符序列的字符串缓冲区 public StringBuffer（int capacity） 初始化指定容量的空内容的字符串（容量是理论值，长度是实际值，默认容量16） public StringBuffer（String str） 初始化指定字符串内容的字符串缓冲区 3. 常用方法 append方法 public StringBuffer append（boolean b） public StringBuffer append（char c） public StringBuffer append（char [ ] str） public StringBuffer append（char [ ] str，int offset，int len） public StringBuffer append（CharSequence s） public StringBuffer append（CharSequence s，int start，int end） public StringBuffer append（double d） public StringBuffer append（float f） public StringBuffer append（int i） public StringBuffer append（long lng） public StringBuffer append（Object obj） public StringBuffer append（String str） public StringBuffer append（StringBuffer sb） insert方法 public StringBuffer insert（int offset，boolean b） public StringBuffer insert（int offset，char c） public StringBuffer insert（int offset。char [ ] str） public StringBuffer insert（int index，char [ ] str，int offset，int len） public StringBuffer insert（int dstOffset，CharSequence s） public StringBuffer insert（int dstOffset, CharSequence s, int start, int end） public StringBuffer insert（int offset, double d） public StringBuffer insert（int offset, float f） public StringBuffer insert（int offset, int i） public StringBuffer insert（int offset, long l） public StringBuffer insert（int offset, Object obj） public StringBuffer insert（int offset, String str） indexOf方法 public int indexOf（String str） public int indexOf（String str，int fromIndex） public int lastIndexOf（String str） public int lastIndexOf（String str，int fromIndex） 常用方法 public int length（）：返回当前长度 public int capacity（）：返回当前容量 public StringBuffer replace（int start，int end，String str）：用一段字符串替换指定位置长度的字符串 public StringBuffer reverse（）：反转字符串 public String substring（int start）：从指定位置截取字符串 public String substring（int start，int end）：截取指定位置长度的字符串 public String toString（）：转为字符串 public void setCharAt（int index，char ch）：将指定位置字符替换为ch public void setLength（int newLength）：设置长度 delete方法 public StringBuffer delete（int start，int end）：删除指定位置开始到指定位置结束的字符串并返回结果 public StringBuffer deleteCharAt（int index）：删除指定位置的字符并返回结果 50. StringBuilder StringBuffer和StringBuilder的区别 线程安全： StringBuffer线程安全 因为StringBuffer的所有公开方法都是synchronized修饰的 StringBuilder线程不安全 缓冲区： StringBuffer每次获取toString都会直接使用缓冲区的toStringCache值来构造字符串 StringBuilder每次都要复制一次字符数组来构造一个字符串 StringBuffer 对缓存区优化，不过 StringBuffer 的这个toString 方法仍然是同步的（synchronized） 性能： StringBuffer 是线程安全的，它的所有公开方法都是同步的 StringBuilder 是没有对方法加锁同步的，性能高于StringBuffer StringBuffer 适用于用在多线程操作同一个 StringBuffer 的场景，如果是单线程场合 StringBuilder 更适合 51. String作为参数 1. 形式参数 基本类型：形式参数的改变不影响实参 引用类型：形式参数的改变直接影响实际参数 String比较特殊，作为参数传递的时候，效果和基本类型一样 package com.demo.javase.demo; /** * @author 絷缘 * @version 1.0 * @date 2020/11/13 13:58 **/ public class StringBufferDemo1 { public static void main(String[] args) { StringBuffer sb1 = new StringBuffer(&quot;hello&quot;); StringBuffer sb2 = new StringBuffer(&quot;world&quot;); System.out.println(&quot;sb1=&quot;+sb1+&quot;,sb2=&quot;+sb2); change(sb1,sb2); System.out.println(&quot;sb1=&quot;+sb1+&quot;,sb2=&quot;+sb2); } public static void change(StringBuffer sb1,StringBuffer sb2){ sb1 = sb2; sb2.append(sb1); System.out.println(&quot;sb1=&quot;+sb1+&quot;,sb2=&quot;+sb2); } } //输出结果为 //sb1=hello,sb2=world //sb1=worldworld,sb2=worldworld //sb1=hello,sb2=worldworld 52. Date类 包：java.util.Date 构造方法： public Date（） ：创建一个Date对象，并初始化为当前时间 public Date（int year，int month，int date）：创建一个Date对象，并初始化为指定时间 year：指定年份-1900 month：0-11 date：1-31 public Date（int year，int month，int date，int hrs，int min，int sec） year：指定年份-1900 month：0-11 date：1-31 hrs：0-23 min：0-59 sec：0-59 public Date（long date）：使用给定毫秒值创建Date对象 public Date（String s）：使用字符串内容创建Date对象并初始化它 常用方法： public String toString（）：格式化日期为YYYY-MM--DD字符串 public static Date valueOf（String s）：将JDBC日期字符串转为Date对象 public int getYear（）：获取年份 public int getMonth（）：获取月份 public int getDate（）：获取日期 public int getDay（）：获取星期几 public int getHours（）：获取时 public int getMinutes（）：获取分 public int getSeconds（）：获取秒 public long getTime（）：获取从1970年开始到现在的毫秒数 public void setYear（int year）：设置年份 public void setMonth（int month）：设置月份 public void setDate（int date）：设置日期 public void setHours（int hours）：设置时 public void setMinutes（int minutes）：设置分 public void setSeconds（int seconds）：设置秒 public void setTime（long time）：设置从1970年开始到现在的毫秒数 53. DateFormat 包：java.text.DateFormat 子类：SimpleDateFormat 构造方法： protected DateFormat（） 成员方法： public String format（Date date）：将日期格式化为日期时间字符串 public Calendar getCalendar（）：将日期时间格式化为日历对象 public TimeZone getTimeZone（）：获取时区 public void setCalendar（Calendar newCalendar） public void setTimeZone（TimeZone zone） public Date parse（String source）：将字符串格式化为Date类型 子类 构造方法 SimpleDateFormat（） SimpleDateFormat（String pattern）：指定模式创建对象 SimpleDateFormat（String pattern，DateFormatSymbols formatSymbols） SimpleDateFormat（String pattern，Locale locale） 成员方法 public void applyLocalizedPattern（String pattern） public void applyPattern（String pattern） public boolean equals（Object obj） public DateFormatSymbols getDateFormatSymbols（） public void setDateFormatSymbols（DateFormatSymbols newFormatSymbols） public StringBuffer format（Date date, StringBuffer toAppendTo, FieldPosition pos） public Date parse（String text, ParsePosition pos） public String toLocalizedPattern（） public String toPattern（） 54. Calendar类 包：java.util.Calendar 构造方法： protected Calendar（）：创建日历对象 protected Calendar（TimeZone zone，Locale aLocale）：创建指定时区和区域设置的日历对象 成员方法： public abstract void add（int field, int amount）：为指定字段添加值 Calendar.YEAR Calendar.MONTH Calendar.DATE Calendar.HOUR Calendar.MINUTE Calendar.SECOND public boolean after（Object when）：判断是否为参数时间之后的时间 public boolean before（Object when）：判断是否为参数时间之前的时间 public boolean equals（Object obj）：判断两个日期是否相同 public String getCalendarType（）：获取Calendar的的日历类型 public int get（int field）：获取指定日历字段的值 Calendar.YEAR Calendar.MONTH Calendar.DATE Calendar.HOUR Calendar.MINUTE Calendar.SECOND public void set（int field, int value）：为指定字段设置值 Calendar.YEAR Calendar.MONTH Calendar.DATE Calendar.HOUR Calendar.MINUTE Calendar.SECOND public static Calendar getInstance（）：使用默认时区获取日历对象 public Date getTime（）：获取Date对象 public long getTimeInMillis（）：获取当前时间毫秒值 public TimeZone getTimeZone（）：获取时区 public void set（int year, int month, int date, int hourOfDay, int minute, int second）：设置字段值 public void setTimeZone（TimeZone value）：设置指定时区 public void setTime（Date date）：使用Date对象设置日历时间 public void setTimeInMillis（long millis）：使用毫秒值设置日历时间 public Instant toInstant（）：将日历对象转为Instant对象 Instant：在时间线上的瞬间点。 调用了Instant的toString方法，输出此瞬间使用ISO-8601表示形式的字符串表示形式。 55. 获取当前时间 时间戳 //方法一：耗时最短 System.currentTimeMillis(); //方法二：耗时最长，效率最低 Calendar.getInstance().getTimeInMillis(); //方法三：耗时第二 new Date().getTime(); 时间 SimpleDateFormat df = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);//设置日期格式 String date = df.format(new Date());// new Date()为获取当前系统时间，也可使用当前时间戳 56. 基本类型包装类 1. 概述 把基本数据类型封装成类类型，然后我们就可以使用类中的方法来处理数据 常用操作：用于基本数据类型和字符串之间的转换 2. 包装类 基本类型：byte short int long float double char boolean 包装类类型：Byte Short Integer Long Float Double Character Boolean 3. Integer 包：java.lang.Integer 字段 BYTES：int型字节数4 MAX_VALUE：int型最大值（2147483647） MIN_VALUE：int型最小值（-2147483648） SIZE：int型二进制位数32 TYPE：类的原始类型为int 构造方法 Integer(int value)：将int类型装箱为Integer类型 Integer(String s)：将数字字符串装箱为Integer类型 常用方法 public static int compare（int x，int y）：比较两个int数字值 public int compareTo（Integer anotherInteger）：比较两个Integer对象 public static int compareUnsigned（int x，int y） ：忽略符号比较两个int数字值 public static Integer decode（String nm）：将String解码为Integer public boolean equals（Object obj）：与指定对象进行比较 public int intValue（）：将Integer转为int public static int parseInt（Stirng s）：将字符串参数解析为（有符号）十进制整数 public static String toBinaryString（int i）：将int型转换为二进制字符串 public static String toHexString（int i）：将int型转换为十六进制字符串 public static String toOctalString（int i）：将int型转换为八进制字符串 public String toString（）：转为字符串 public static String toString（int i）：把int转为String public static String toString（int i，int radix）：把int转为指定进制字符串，radix为基数 public static Integer valueOf（int i）：将int类型转为Integer对象 public static Integer valueOf（String s）：将数字字符串转为Integer对象 最大进制为36进制：0-9，A-Z 4. 自动拆箱装箱 从JDK1.5+，支持自动装箱（基本数据类型 -&gt; 包装类型），支持自动拆箱（包装类型 -&gt; 基本数据类型） Integer it = new Integer(555); || Integer it = 555; //自动装箱：把基本数据类型自动装换为包装类类型 it = it + 666; //自动拆箱：把包装类类型自动转换为基本数据类型 //如果it为null，会报NullPointerException异常 Integer it2 = null; it2 += 10; 5. Character 字段 构造方法 public Character（char value）：将char类型装箱成Character 成员方法 public char charValue（）：返回Character的值 public static int compare（char x，char y）：比较两个char值 public int compareTo（Charater anotherCharacter）：比较两个Character对象 public boolean equals（Object obj）：比较两个Character对象 public static boolean isDigit（char ch）：确定字符是否是数字 public static boolean isLetter（char ch）：确定字符是否为一个字母 public static boolean isLetterOrDigit（char ch）：确定指定字符是字母还是数字 public static boolean isLowerCase（char ch）：确定字符是否为小写字符 public static boolean isUpperCase（char ch）：确定字符是否为大写字符 public static boolean isSpaceChar（char ch）：确定字符是否为空格字符 public static boolean isWhitespace（char ch）：确定字符是否为空格字符 public static char toLowerCase（char ch）：字符转小写 public static char toUpperCase（cahr ch）：字符转大写 public String toString（） public static Character valueOf（char c）：将char装箱为Character对象 6. Boolean 成员变量 FALSE TRUE TYPE 构造方法 public Boolean（boolean value）：只能是true和false public Boolean（String s）：只有字符串为true时，为true，其它都为false 成员方法 public boolean booleanValue（）：将Boolean拆箱为boolean类型 public static int compareTo（Boolean b）：比较两个Boolean类型 public boolean equals（Object obj）：比较boolean的值是否相等 public static boolean parseBoolean（String s）：将字符串转为boolean值 public static Boolean valueOf（boolean b）：将boolean装箱为Boolean类型 public static Boolean valueOf（String s）：将字符串转为Boolean类型 7. Double 包：java.lang.Double 字段 BYTES：用于表示double型字节数 MAX_VALUE：最大正有限值 MIN_VALUE：最小的非零有限值 NaN：Not a Number SIZE：double所占位数 TYPE：返回类型 构造方法 public Double（double value）：将double类型变量装箱为Double类型对象 public Double（String s）：通过字符串的值创建一个Double对象 成员方法 public byte byteValue（）：将Double对象拆箱为byte类型 public int compareTo（Double anotherDouble）：比较两个Double对象 public double doubleValue（）：将Double对象拆箱为double对象 public boolean equals（Object obj）：比较两个对象的值 public float floatValue（）：将Double对象拆箱为float类型 public int intValue（）：返回Double对象的int值 public boolean isNaN（）：判断是否为非数字 public static double parseDouble（Stirng s）：将String转为double型 public short shortValue（）：返回Double对象的short值 public static String toHexString（double d）：返回double类型的十六进制字符串 public static Double valueOf（double d）：将double 装箱为Double对象 public static Double valueOf（String s）：将字符串转为Double对象 57. 正则表达式 1. 概述 是一种用来描述匹配特定规则文本的字符串 2. 正则表达式的规则 本处仅描述常用规则，其他详见 “正则表达式.md” 普通字符 a，b，c....... 0，1，2....... 转义字符 \\r：回车符 \\n：换行符 \\s：任意空字符 \\t：制表符 \\f：换页符 预定义字符 点：任意字符 \\d：所有数字 \\D：非数字 \\b：单词边界 \\B：非单词边界 \\w：单词字符 \\W：非单词字符 字符类 [abc]：a或b或c [^abc]：除了a，b，c [a-zA-Z]：所有大小写字母 [0-9]：0到9所有数字 边界匹配器 ^：行的开头 $：行的结尾 数量词 ？：一次或零次 *：零次或多次 +：一次或多次 {n}：出现n次 {n,m}：出现n到m次 {n,}：至少出现n次 3. 正则表达式的应用 判断 public boolean matches（String regex） 分割 public String [ ] split（String regex） 替换 public String replaceAll（String regex，String replacement） 获取 Pattern类和Matcher类 //将正则表达式编译为模式对象 Pattern p = compile(&quot;\\\\d+&quot;); //通过模式对象获取匹配器对象 Matcher m = p.matcher(&quot;zhiyuandnc666&quot;); //调用匹配器的功能 boolean b = m.matches(); boolean b2 = m.lookingAt(); System.out.println(b); System.out.println(b2); System.out.println(b3); //返回结果 //false //false //true String str = &quot;momo is a da shuai ge,zhen de shi yi ge da shuai ge,da shuai ge,shuai ge.&quot;; Pattern p2 = Pattern.compile(&quot;\\\\b\\\\w{3}\\\\b&quot;); Matcher m2 = p2.matcher(str); while(m2.find()){ String group = m2.group(); System.out.println(group); } 58. BigInteger 包：java.math.BigInteger 字段 ONE：常数一 TEN：常数十 ZERO：常数零 构造方法 public BigInteger（byte [ ] val） public BigInteger（int signum，byte [ ] magnitude） public BigInteger（String val） 成员方法 public BigInteger add（BigInteger val）：加法运算 public BigInteger subtract（BigInteger val）：减法运算 public BigInteger divide（BigInteger val）：除法运算 public BigInteger multiply（BigInteger val）：乘法运算 public BigInteger remainder（BigInteger val）：取余运算 public BigInteger pow（int exponent）：次方运算 public BigInteger [ ] divideAndRemainder（BigInteger val）：返回包含商和余数的BigInteger数组 public int intValue（）：转为int 59. BigDecimal 浮点型数据操作容易损失精度 System.out.println(0.01+0.09); System.out.println(0.08+0.02); System.out.println(1.0-0.33); System.out.println(1.015*100); System.out.println(1.211/100); //输出结果 0.09999999999999999 0.1 0.6699999999999999 101.49999999999999 0.012110000000000001 因为浮点型的存储形式参考：https://blog.csdn.net/u014470361/article/details/79820892，导致超过16位的浮点数的精度丢失，我们需要更精确的结果，所以出现了BigDecimal类型，用于对超过16位有效位浮点数进行精确运算 包：java.math.BigDecimal 字段： 构造方法 BigDecimal(int) 创建一个具有参数所指定整数值的对象 BigDecimal(double) 创建一个具有参数所指定双精度值的对象 BigDecimal(long) 创建一个具有参数所指定长整数值的对象 BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象 成员方法 add(BigDecimal) BigDecimal对象中的值相加，返回BigDecimal对象 subtract(BigDecimal) BigDecimal对象中的值相减，返回BigDecimal对象 multiply(BigDecimal) BigDecimal对象中的值相乘，返回BigDecimal对象 divide(BigDecimal) BigDecimal对象中的值相除，返回BigDecimal对象 toString() 将BigDecimal对象中的值转换成字符串 doubleValue() 将BigDecimal对象中的值转换成双精度数 floatValue() 将BigDecimal对象中的值转换成单精度数 longValue() 将BigDecimal对象中的值转换成长整数 intValue() 将BigDecimal对象中的值转换成整数 60. StringTokenizer 包：java.util.StringTokenizer 构造方法： public StringTokenizer（String str） public StringTokenizer（String str，String delim） str：字符串 delim：分隔符 public StringTokenizer（String str，String delim，boolean returDelims） str：字符串 delim：分隔符 returnDelims：是否返回分隔符 成员方法 public int countTokens（）：统计分隔符出现的次数 public boolean hasMoreElements（）：判断字符串中是否存在下一个元素 public Object nextElement（）：获取字符串中的下一个元素 61. 集合 概述：集合就是java中用来存储多个不同引用类型的对象的容器 数组和集合的区别 数组长度固定，既可以存储普通类型，也可以存储引用类型，但是只能存一种数据类型 集合长度可变，只能存储引用类型，但是可以存储不同引用类型 集合特点 智能存储对象，集合长度是可变的，集合可以存储不同类型的对象 集合框架 一. Collection集合（顶层接口） 包：java.util.Collection Collection集合层次结构中的顶层接口 常用方法 public boolean add（E e）：将指定元素加入集合 public boolean addAll （Collection&lt;? extends E&gt;）：将指定集合所有元素加入集合 public void clear（）：删除集合中所有元素 public boolean contains（Object o）：判断集合中是否存在指定元素 public boolean containsAll（Collection&lt;?&gt; c）：判断集合中是否存在指定集合所有元素 public boolean equals（Object o）：比较指定对象与集合是否相等 public int hashCode（）：返回此集合的哈希码值 public boolean isEmpty（）：判断集合是否为空 public Iterator iterator（）：返回集合的元素迭代器 public boolean remove（Object o）：从集合中移除指定元素 public boolean removeAll（Collection&lt;?&gt; c）：从集合中移除指定集合中所有元素 public boolean retainAll（Collection&lt;?&gt; c）：仅保留集合中指定集合的所有元素，返回原集合是否被修改 原集合如果没有指定集合中的元素，原集合置空 如果两个集合元素相同，原集合保持不变 如果原集合包含指定集合元素，则保留两者交集 public int size（）：返回集合的元素个数 public Object[ ] toArray（）：将集合转为数组 Collection集合框架 1. List接口（继承自Collection接口，JDK1.2）【列表】 包：java.util.List List接口继承自Collection接口 特点： 有序的集合（存储和取出元素顺序相同） 允许存储重复的元素 有索引，可以使用for循环直接遍历（这也是有序的一个表现） 实现类： ArrayList 结构是数组，查询快，增删慢：线程不安全，效率高 LinkedList 结构是链表，查询慢，增删快：线程不安全，效率高 Vector 结构是数组，查询快，增删慢：线程安全，效率低 Stack 常用方法： public void add（int index，E element）：将指定元素加入到集合指定索引处 public boolean addAll（int index，Collection&lt;? extends E&gt; c）：将指定集合从指定索引处加入集合 public E get（int index）：获取集合中指定索引处元素值 public int indexOf（Object o）：指定元素在集合中第一次出现的索引值 public int lastIndexOf（Object o）：指定元素在集合中最后一次出现的索引值 public ListIterator listIterator（） public ListIterator listIterator（int index） public E remove（int index）：移除集合中指定索引的元素 public boolean remove（Object o）：移除集合中指定元素 public boolean removeAll（Collection&lt;?&gt; c）：从集合中移除指定集合中的内容 public E set（int index，E element）：设置集合中指定索引处元素值 public int size（）：获取集合元素个数 public Object[ ] toArray（）：将集合转为Object数组 public List subList（int fromIndex，int toIndex）：从集合中取出一段集合 2. Set接口（继承自Collection接口，JDK1.2）【集合】 包：java.util.Set Set接口继承自Collection接口 特点： 无序的集合 不允许有重复的元素 没有索引，无法用for循环直接遍历，需要迭代器（这也是无序的一个表现） 实现类： TreeSet HashSet LinkedHashSet 常用方法： public boolean add（E e）：若指定元素在集合中不存在，则加入集合 public boolean addAll（Collection &lt;? extends E&gt; c）：若指定集合中的元素在集合中不存在，则加入集合 public void clear（）：删除集合中所有元素 public boolean contains（Object o）：判断集合中是否包含指定元素 public boolean containsAll（Collection&lt;? extends E&gt; c）：判断集合中是否包含制定集合中的所有元素 public boolean equals（Object o）：比较两个集合是否相等 public boolean isEmpty（）：判断集合是否为空 public Iterator iterator（）：返回集合元素迭代器 public boolean remove（Object o）：从集合中删除指定元素 public boolean removeAll（Collection&lt;? extends E&gt; c）：从集合中删除指定集合中包含的所有元素 public boolean retainAll（Collection&lt;? extends E&gt; c）：取交集 public int size（）：返回集合中元素的个数 public Object[ ] toArray（）：返回集合对象数组 public T[ ] toArray（T [ ] a）：返回指定类型的集合对象数组 3. Queue接口（继承自Collection接口，JDK1.5）【队列】 Queue接口继承自Collection接口 注意事项： Queue使用时要尽量避免Collection的add()和remove()方法，而是要使用offer()来加入元素，使用poll()来获取并移出元素，它们的优点是通过返回值可以判断是否成功，add()和remove()方法在失败的时候会抛出异常 LinkedList类实现了Queue接口，因此我们可以把LinkedList当成Queue来用 特点： 先进先出，先添加的元素，最先被删除 队列是一种特殊的线性表，他只允许在表的前端删除元素，在表的后端插入元素 实现类： ArrayDeque PriorityQueue Queue集合框架 （1）. Deque接口（继承自Queue接口）【双端队列】 Deque接口继承自Queue接口，同时具有队列和栈的功能 实现类 LinkedList：使用双向链表 ArrayDeque：使用循环数组实现双向队列 （2）. AbstractQueue类（实现自Queue接口） 子类 PriorityQueue：优先队列，使用数组实现堆的结构，线程不安全 PriorityBlockingQueue：优先阻塞队列，使用ReentrantLock锁来保持同步，线程安全 二. Map集合（顶层接口） 包：java.util.Map Map集合层次接口中的顶层接口 常用方法： public void clear（）：删除Map集合中所有元素 public boolean containsKey（Object key）：判断集合中是否包含指定键 public boolean containsValue（Object value）：判断集合中是否包含指定值 public boolean equals（Object o）：判断集合是否相同 public boolean isEmpty（）：判断集合是否为空 public V get（Object key）：获取指定键对应的值 public Set keySet（）：获取Map集合中键的Set集合 public Set&lt;Map.Entry&lt;K，V&gt;&gt; entrySet（）：获取Map集合映射关系的Set集合 public V put（K key，V value）：将指定键值对加入Map集合 public void putAll（Map&lt;? extends K，? extends V&gt; m）：将指定集合中的所有映射加入到当前集合 public int size（）：返回Map集合中映射数量 public Collection values（）：获取Map集合中值的Collection集合 Map集合框架 三. Iterator迭代器 迭代器是一种设计模式，它是一个对象，它可以遍历并选择序列中的对象，而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象，因为创建它的代价小。 包：java.util.Iterator 获取迭代器对象的方式 iterator（） 常用方法： next（）：获取集合中的下一个元素 hasNext（）：判断集合中是否存在下一个元素 remove（）：删除迭代器已迭代元素 public static void main(String[] args) { Collection c = new ArrayList(); c.add(&quot;String&quot;); c.add(123); c.add(true); c.add('a'); c.add(12.0); System.out.println(c); Iterator it = c.iterator(); while(it.hasNext()){ System.out.println(it.next()); } for(;it.hasNext();){ System.out.println(it.next()); } } 四. Spliterator接口（JDK1.8） 包：java.util.Spliterator Spliterator是Java 8中加入的一个新接口；这个名字代表“可拆分迭代器”（splitable iterator）。 和Iterator一样，Spliterator也用于遍历数据源中的元素，但它是为了并行执行而设计的。 Java 8已经为集合框架中包含的所有数据结构提供了一个默认的Spliterator实现。 集合实现了Spliterator接口，接口提供了一个spliterator方法。 获取迭代器对象的方法： spliterator（） 五. ListIterator接口（JDK1.2） 包：java.util.ListIterator 常用方法： public void add（E e） public boolean hasNext（） public boolean hasPrevious（） public E next（） public int nextIndex（） public E previous（） public int previousIndex（） public void remove（） public void set（E e） 62. ArrayList可变数组 数组的长度不可以发生改变 ArrayList的长度可以发生改变 包：java.util.ArrayList 构造方法： ArrayList（）：构造一个初始容量为10的空列表 ArrayList（Collection&lt;? extends E&gt; c）：构造一个包含指定集合的元素列表 ArrayList（int initialCapacity）：构造具有初始容量的空列表 ArrayList 代表泛型 泛型：也就是装在集合中的所有元素属于什么类型，注意泛型只能是引用类型，不能是基本类型 1. 格式： ArrayList&lt;String&gt; list = new ArrayList&lt;String&gt;(); ArrayList list = new ArrayList(); 从JDK1.8开始，右侧尖括号内部可以不写内容，默认与左边相同，JDK1.8之前需要写明内容，但是建议写上 注意事项： 对于ArrayList集合来说，直接打印得到的不是地址值，而是内容，如果内容为空，得到的是空的中括号 2. 向集合中添加一些数据，需要用到add方法 list.add(&quot;赵丽颖&quot;); list.add(&quot;古力娜扎&quot;); list.add(&quot;迪丽热巴&quot;); list.add(&quot;胡歌&quot;); System.out.println(list); 注意事项： 添加的数据类型必须为创建ArrayList集合时使用的类型，否则为错误写法 3. 常用方法 public boolean add（E e）：向列表中添加元素，参数类型和创建列表时的泛型一致，返回值为添加是否成功 public E get（int index）：从列表中获取元素，参数是索引编号，返回值就是对应位置的元素 public E set（int index，E element）：为列表中指定位置的元素设置值 public E remove（int index）：从列表当中删除元素，参数是索引编号，返回值就是被删除的元素 public int size（）：获取列表的尺寸，返回值是集合中元素的个数 public void clear（）：从列表中删除所有元素 public boolean addAll（Collection c）：将指定集合的内容（按迭代器返回的顺序）追加到列表的末尾 public boolean contains（Object o）：如果列表中包含指定元素，则返回true public int indexOf（Object o）：返回此列表指定元素第一次出现的位置，如果不存在，返回-1 public int lastIndexOf（Object o）：返回此列表指定元素最后一次出现的位置，如果不存在，则返回-1 public boolean isEmpty（）：判断列表是否为空 public Object [ ] toArray（）：将列表转为数组 public void trimToSize（）：将列表的容量修改为列表当前大小 4. 遍历ArrayList集合 for(int i = 0;i &lt; list.size();i++){ System.out.println(list.get(i)); } 5. ArrayList存储基本数据类型 如果希望ArrayList当中存储基本数据类型，就必须使用基本数据类型的 “包装类” 包装类：是引用类型，包装类都位于java.lang包下 基本数据类型 包装类 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean 从JDK1.5+，支持自动装箱（基本数据类型 -&gt; 包装类型），支持自动拆箱（包装类型 -&gt; 基本数据类型） 定义格式 ArrayList&lt;Integer&gt; list = new ArrayList&lt;Integer&gt;(); 6. ArrayList的遍历(三种) import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ArrayListDemo { public static void main(String args[]){ List&lt;String&gt; list = new ArrayList&lt;String&gt;(); list.add(&quot;鞠婧祎&quot;); list.add(&quot;佟丽娅&quot;); list.add(&quot;迪丽热巴&quot;); //方法一：Iterator（迭代器）遍历 Iterator it1 = list.iterator(); while(it1.hasNext()){ System.out.println(it1.next()); } for(Iterator it2;it2.hasNext();){ System.out.println(it2.next()); } //方法二：foreach遍历 for(String li:list){ System.out.println(li); } //方法三：for循环遍历 for(int i = 0;i &lt; list.size();i++){ System.out.println(list.get(i)); } } 63. LinkedList 包：java.util.LinkedList 构造方法： LinkedList（）：构造一个空链表 LinkedList（Collection&lt;? extends E&gt; c）：构造一个包含指定集合的空链表 1. 格式： LinkedList&lt;Student&gt; list = new LinkedList&lt;Student&gt;(); 2. 常用方法： public boolean add（E e）：将指定元素追加到集合末尾 public void add（int index，E element）：在集合中指定位置插入指定元素 public boolean addAll（Collection&lt;? extends E&gt; c）：将指定集合内容加入到集合末尾 public boolean addAll（int index，Collection&lt;? extends E&gt; c）：将指定集合内容从指定位置加入到集合 public void addFirst（E e）：在集合首部加入指定元素 public void addLast（E e）：在集合末尾加入指定元素 public void clear（）：删除集合中所有元素 public boolean contains（Object o）：判断集合中是否包含指定元素 public E element（）： public E get（int index）：获取集合中指定位置的元素 public E getFirst（）：返回集合中第一个元素 public E getLast（）：返回集合中最后一个元素 public int indexOf（Object o）：返回指定元素在集合中第一次出现的位置 public int lastIndexOf（Object o）：返回指定元素在集合中最后一个出现的位置 public ListIterator listIterator（int index）：获取迭代器 public boolean offer（E e）：将指定元素添加到集合的尾部 public boolean offerFirst（E e）：将指定元素添加到集合的首部 public boolean offerLast（E e）：将指定元素添加到集合的尾部 public E element（）：检索但不删除链表的头部（第一个元素） public E peek（）：检索但不删除链表的头部（第一个元素） public E peekFirst（）：检索但不删除链表的第一个元素，如果链表为空，返回null public E peekLast（）：检索但不删除链表的最后一个元素，如果链表为空，返回null public E poll（）：检索并删除链表的头（第一个元素） public E pollFirst（）：检索并删除链表的第一个元素，如果链表为空，返回null public E pollLast（）：检索并删除链表的最后一个元素，如果链表为空，返回null public E pop（）： public void push（E e）： public E remove（）：检索并删除链表的头（第一个元素） public E remove（int index）：删除链表中指定位置元素 public E removeFirst（） public E removeLast（） public E set（int index，E element） public int size（） public Spliterator spliterator（） public Object[ ] toArray（） public T[ ] toArray（T[ ] a） 64. Vector 包：java.util.Vectory 构造方法： Vector（）：构造一个初始容量为10的空集合 Vector（Collection&lt;? extends E&gt; c）：构造一个包含指定集合的集合 Vector（int initialCapacity）：构造一个指定容量的集合 Vector（int initialCapacity，int capacityIncrement）：构造一个指定容量指定增量的集合 1. 格式： Vector v = new Vector(16,2); 2. 常用方法： public boolean add（E e）：将指定元素追加到集合末尾 public void add（int index，E element）：将指定元素插入到集合指定位置 public boolean addAll（Collection&lt;? extends E&gt; c）：将指定集合中所有内容追加到当前集合末尾 public boolean addAll（int index，Collection&lt;? extends E&gt; c）：将指定集合中所有内容插入到集合指定位置 public void addElement（E obj）：将指定组件添加到集合的末尾 public int capacity（）：获取集合容量 public void clear（）：删除集合中所有元素 public boolean contains（Object o）：判断集合中是否包含指定元素 public boolean containsAll（Collection&lt;?&gt; c）：判断集合中是否包含指定集合的所有元素 public void copyInto（Object[ ] anArray）：将集合复制到指定数组中 public E elementAt（int index）：获取指定索引处的元素 public Enumeration elements（）：获取集合的元素枚举 public boolean equals（Object o）：将集合与指定对象比较 public E firstElement（）：返回集合的第一个元素 public void forEach（Consumer&lt;? super E&gt; action）： public E get（int index）：获取指定位置的元素 public int indexOf（Object o）：返回集合中指定元素第一次出现的索引 public int indexOf（Object o，int index）：返回集合中指定元素从指定位置开始第一次出现的索引 public boolean isEmpty（）：检查集合是否为空 public Iterator iterator（）：迭代器 public E lastElement（）：返回集合中最后一个元素 public int lastIndexOf（Object o）：返回集合中指定元素最后一次出现的索引 public int lastIndexOf（Object o，int index）：返回集合中指定元素从指定位置开始最后一次出现的索引 public ListIterator listIterator（）：获取迭代器 public E remove（int index）：删除集合中指定元素 public boolean removeAll（Collection&lt;?&gt; c）：从集合中删除指定集合的所有元素 public void replaceAll（） public boolean retainAll（Collection&lt;?&gt; c）：保留两集合的交集 public E set（int index，E element）：设置集合中指定位置的元素值 public void setSize（int newSize）：设置集合容量 public int size（）：获取集合元素个数 public List subList（int fromIndex，int toIndex）：从集合中截取一段集合 public Object [ ] toArray（）：转Object数组 public T [ ] toArray（T [ ] a）：转指定类型数组 public void trimToSize（）：修改容量 65. Enumeration：java.util中的接口，现已被Iterator取代，与enum没有关系 66. 泛型 概述：限定统一存储数据类型的一种特殊类型 泛型的实现是靠类型擦除技术 类型擦除是在编译期完成的 也就是在编译期 编译器会将泛型的类型参数都擦除成它的限定类型，如果没有则擦除为object类型之后在获取的时候再强制类型转换为对应的类型。 在运行期间并没有泛型的任何信息，因此也没有优化。 格式：&lt;数据类型&gt; 数据类型必须是引用类型 好处： 类型参数化，让应用类型像参数一样传递 使运行时异常变为编译时异常，减少了编译时的类型检查 泛型方法 减少类型转换，提高代码的安全性 用法： 泛型接口 泛型抽象类 泛型类 方法参数类型约束 方法返回类型约束 1. 泛型类 public class 类名&lt;泛型类型&gt;{} 2. 泛型方法 public &lt;泛型返回类型&gt; 返回类型 方法名(泛型类型形参){} 3. 泛型接口 interface 接口名&lt;泛型类型&gt;{} 4. 泛型抽象类 public abstract class 类名&lt;泛型类型&gt;{} 5. 泛型通配符 &lt;?&gt;任意类型，如果不明确，那就是Object以及任意java类 &lt;? extends E&gt;向下限定，E及其子类 &lt;? super E&gt;向上限定，E及其父类 67. 增强for循环 for(元素数据类型 临时变量:多元素容器){ System.out.println(临时变量); } 实现原理 for (Integer i : list) { System.out.println(i); } 反编译后 Integer i; for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){ i = (Integer)iterator.next(); } 68. 静态导入 直接导入到方法级别（方法必须是静态的） import static packageName.className.methodName； 注意事项： 方法必须为静态方法 如果有多个静态方法，就不知道用哪个，就需要加方法的绝对包路径进行调用 例： import static java.lang.Math.abs; import static java.lang.Math.pow; import static java.lang.Math.max; public class Demo{ public static void main(String [] args){ System.out.println(abs(111)); System.out.println(pow(3,2)); System.out.println(max(3,2)); } } 69. 可变参数 概述： 不知道方法将来需要多少个参数 格式： 修饰符 返回值类型 方法名（数据类型... 变量名）{ } 注意： 这里的变量其实是一个数组 如果一个方法既有可变参，也有其他参数，那么，可变参必须是最后一个 可变参只能有一个，且放在参数列表最后 例： public static int sum(int... a){ return a; } public static String method(Object... o){ return o.toString(); } 70. TreeSet 包：java.util.TreeSet 父类：java.util.AbstractSet 实现了SortedSet接口 构造方法： public TreeSet（）：构造一个空的集合 public TreeSet（Collection&lt;? extends E&gt; c）：构造一个包含指定集合元素的新集合，根据元素自然排序进行排序 public TreeSet（Comparator&lt;? super E&gt; comparator）：按照指定比较器的顺序构造一个空的集合 public TreeSet（SortedSet s）：构造一个包含指定集合元素的新集合，按照默认排序 常用方法： public boolean add（E e）：将指定元素添加到集合中 public boolean addAll（Collection&lt;? extends E&gt; c）：将指定集合中所有元素加入到集合中 public E ceiling（E e）：查找集合中大于等于指定元素的最小元素，如果没有，返回null public Object clone（）：返回集合的浅拷贝 public void clear（）：删除集合中所有元素 public boolean contains（Object o）：判断集合中是否包含指定元素 public E first（）：返回集合中第一个元素 public E floor（E e）：查找集合中小于等于指定元素的最大元素，如果没有，返回null public boolean isEmpty（）：判断集合是否为空 public Iterator iterator（）：以升序返回集合元素迭代器 public Iterator descendingIterator（）：以降序返回集合元素迭代器 public NavigableSet descendingSet（）：返回集合元素反向排序集合 public E last（）：返回集合中最后一个元素 public E lower（E e）：查找集合中小于指定元素的最大元素，如果没有，返回null public E higher（E e）：查找集合中大于指定元素的最小元素，如果没有，返回null public E pollFirst（）：检索并删除集合中第一个元素 public E pollLast（）：检索并删除集合中最后一个元素 public boolean remove（Object o）：从集合中删除指定元素 public int size（）：返回集合中元素个数 public Spliterator spliterator（） public NavigableSet subSet（E fromElement，boolean fromInclusive，E toElement，boolean toInclusive）：截取一段集合 public SortedSet subSet（E fromElement，E toElement）：截取一段集合 public SortedSet tailSet（E fromElement）：返回集合中大于等于指定元素的部分集合 public NavigableSet tailSet（E fromElement，boolean inclusive） 实现原理 比较器 包：java.lang.Comparable 红黑树完成的自然排序实际上是依赖于Comparable接口中的compareTo（）方法，所以想要按照自己的方式进行数据排序，必须实现此接口并重写该方法 需要比较的对象的类去实现Comparable接口并重写compareTo（）方法 需要比较的对象的类也可以去实现Comparator接口并重写compare（）方法 Comparable和Comparator的区别 71. HashSet 底层数据结构是哈希表（元素是链表的数组） 包：java.util.HashSet 父类：java.util.AbstractSet 构造方法：（初始容量16，负载因子0.75） public HashSet（）：构造一个空集合 public HashSet（Collection&lt;? extends E&gt; c）：构造一个包含指定集合中所有元素的集合 public HashSet（int initialCapacity）：构造一个之指定容量的空集合 public HashSet（int initialCapacity，float loadFactor）：构造一个指定容量，指定负载因子的空集合 常用方法： public boolean add（E e）：将指定元素加入集合 public void clear（）：删除集合中所有元素 public Object clone（）：返回HashSet的浅拷贝 public boolean isEmpty（）：判断集合是否为空 Iterator iterator（）：返回集合元素迭代器 public boolean remove（Object o）：移除集合中指定元素 public int size（）：返回集合中元素个数 72. LinkedHashSet 底层数据结构是哈希表（元素是双向链表的数组） 元素存取有序 包：java.util.LinkedHashSet 父类：java.util.HashSet 构造方法： public LinkedHashSet（）：构造一个默认容量16，负载因子0.75的空集合 public LinkedHashSet（Collection&lt;? extends E&gt; c）：构造包含指定集合的集合 public LinkedHashSet（int initialCapacity）：构造指定初始容量的空集合 public LinkedHashSet（int initialCapacity，float loadFactor）：构造指定初始容量和指定负载因子的空集合 常用方法：继承自父类（HashSet的方法） public boolean add（E e）：将指定元素加入集合 public void clear（）：删除集合中所有元素 public Object clone（）：返回HashSet的浅拷贝 public boolean isEmpty（）：判断集合是否为空 Iterator iterator（）：返回集合元素迭代器 public boolean remove（Object o）：移除集合中指定元素 public int size（）：返回集合中元素个数 73. HashMap 包：java.util.HashMap 构造方法： public HashMap（）：构造一个空的HashMap集合，初始容量为16，负载因子为0.75 public HashMap（int initialCapacity）：构造一个指定容量的HashMap集合 public HashMap（int initialCapacity，int loadFactor）：构造一个具有指定容量和指定负载因子的HashMap集合 public HashMap（Map&lt;? extends K,? extends V&gt; m）：构造一个包含指定Map集合所有映射关系的HashMap集合 常用方法： public void clear（）：删除集合中所有映射关系 public Object clone（）：获得集合的浅拷贝 public boolean containsKey（Object key）：判断集合中是否包含指定键 public boolean containsValue（Object value）：判断集合中是否包含指定值 public boolean isEmpty（）：判断集合是否为空 public V get（Object key）：获取指定键对应的值 public Set keySet（）：获取Map集合中键的Set集合 public Set&lt;Map.Entry&lt;K，V&gt;&gt; entrySet（）：获取Map集合映射关系的Set集合 public V put（K key，V value）：将指定键值对加入Map集合（若指定键存在，则将指定值与之关联，说白了就是修改映射关系） public void putAll（Map&lt;? extends K，? extends V&gt; m）：将指定集合中的所有映射加入到当前集合 public int size（）：返回Map集合中映射数量 public Collection values（）：获取Map集合中值的Collection集合 public V remove（Object key）：删除指定键对应的键值对 public boolean remove（Object key，Object value）：删除唯一对应关系的键值对 74.LinkedHashMap 包：java.util.LinkedHashMap 父类：java.util.HashMap 构造方法： public LinkedHashMap（）：构造一个空的LinkedHashMap集合，初始容量为16，负载因子为0.75 public LinkedHashMap（int initialCapacity）：构造一个指定容量的LinkedHashMap集合 public LinkedHashMap（int initialCapacity，int loadFactor）：构造一个具有指定容量和指定负载因子的LinkedHashMap集合 public LinkedHashMap（Map&lt;? extends K,? extends V&gt; m）：构造一个包含指定Map集合所有映射关系的LinkedHashMap集合 public LinkedHashMap（int initialCapacity，int loadFactor，boolean accessOrder）：构造一个具有指定初始容量和指定负载因子以及制定订购模式的LinkedHashMap集合 常用方法： public void clear（）：删除集合中所有映射关系 public boolean containsValue（Object value）：判断集合中是否存在指定值 public Set&lt;Map.Entry&lt;K,V&gt;&gt; entrySet（）：获取集合中所有映射关系组成的Set集合 public V get（Object key）：获取指定键对应的值 public Set keySet（）：获取集合中所有键组成的Set集合 public Collection values（）：获取集合中所有键组成的Collection集合 75. TreeMap 包：java.util.TreeMap 父类：java.util.AbstractMap 构造方法： public TreeMap（）：构造一个（默认使用键自然排序）空TreeMap集合 public TreeMap（Comparator&lt;? super K&gt; comparator）：构造一个（按照指定比较器规则排序）空TreeMap集合 public TreeMap（Map&lt;? extends K，? extends V&gt; m）：构造一个包含指定Map集合所有映射关系的（按照指定Map集合中key的自然排序）TreeMap集合 public TreeMap（SortedMap&lt;K，? extends V&gt; m）：构造一个包含指定SortedMap集合中所有映射关系的并按照SortedMap中的排序规则排序的TreeMap集合 常用方法： public void clear（）：删除集合中所有映射关系 public Object clone（）：获取集合的浅拷贝 public Comparator&lt;? super K&gt; comparator（）：返回集合的比较器 public boolean containsKey（Object key）：判断集合中是否包含指定键 public boolean containsValue（Object value）：判断集合中是否包含指定值 public NavigableSet descendingKeySet（）：返回相反顺序的键的Set集合 public NavigableMap&lt;K，V&gt; descendingMap（）：返回相反顺序的映射关系的Map集合 public Set&lt;Map.Entry&lt;K,V&gt;&gt; entrySet（）：获取集合中所有映射关系组成的Set集合 public Map.Entry&lt;K，V&gt; firstEntry（）：获取集合中第一个键对应的映射关系 public K firstKey（）：获取集合中第一个键的值 public Map.Entry&lt;K，V&gt; floorEntry（）：获取集合中最后一个键对应的映射关系 public K floorKey（）：获取集合中最后一个键的值 public V get（Object key）：获取指定键对应的值 public Set keySet（）：获取集合中所有键构成的Set集合 public Collection values（）：获取集合中所有值构成的Collection集合 public V put（K key，V value）：将指定映射关系加入集合中，若指定键已存在，则修改对应值 public void putAll（Map&lt;? extends K，? extends V&gt; m）：将指定Map集合中所有映射关系加入集合中 public V remove（Object key）：删除指定键对应的映射关系 public int size（）：返回集合中映射数量 76. 异常 1. 分类 运行时异常：所有RuntimeException类及子类都属于运行时异常 运行时异常一般是代码问题，提高代码健壮性 编译时异常：除运行时异常外都属于编译时异常 编译时异常必须显示处理，否则无法编译通过 自定义异常 2. 处理异常 try...catch...finally 变形格式：try...catch try{ //try里的代码越少越好，因为每捕获一次异常，JVM就会启动分配一次资源 }catch(异常名 变量名){ //catch里面必须有内容，哪怕只给一个简单的提示 } 一个异常的处理，写一个try{}catch{} 多个异常的处理，写一个try{}，写多个catch(){}，但是一个一场被捕获到就直接走catch语句了，无法捕获另外的异常 注意事项： 能明确的异常尽量明确，实在不明确异常再使用Exception 如果有明确的异常，同级的异常前后无所谓，不同级的异常，明确的异常在前，Exception必须放置于最后，因为Exception可以匹配所有异常，是明确异常的父类的父类 JDK1.7出现了一个新的异常处理方案： try{ }catch(Exception1 | Exception2 | Exception3 | ... variableName){ } 可以通过或将多个异常连接在一起，使得catch可以同时处理多个异常，但是**注意：这种方式异常中不可以有Exception** 异常中需要了解的方法： public String getMessage（）：返回异常的详细信息 public String toString（）：返回异常的简单介绍 这个对象的类的name（全路径名） “：”（一个冒号和一个空格） 调用这个对象的**getLocalizedMessage()方法的结果，默认返回getMessage()** public void printStackTrace（）：将toString的内容以及异常出现的位置输出到控制台 public void printStackTrace（PrintStream s）：将异常内容保存到日志文件中，方便查看 throws：有些异常我们无法处理或者权限不够时，就需要将异常抛出 throws必须跟在方法的括号后面 编译期异常被抛出，将来的调用者必须处理 运行时异常被抛出，将来的调用者可以不处理 throws后可以跟多个异常，中间用逗号隔开 throw：如果出现了异常情况，我们需要抛出该异常的对象，使用throw new 异常名（）； 使用throw一定抛出了异常 使用throws可能抛出异常，并不一定会发生异常 throw和throws的区别 放在方法声明后面，跟的是异常类名 可以跟多个异常类名，用逗号隔开 表示抛出异常，由该方法的调用者来处理 throws表示出现异常的可能性，并不一定会抛出异常 放在方法体内，跟的是对象名 只能抛出一个异常对象 表示抛出异常，有方法体内语句处理 throw表示抛出了异常，执行throw一定会抛出异常 finally 格式：try{}catch(){}finally{} finally 里的语句一定会被执行 用于释放资源，在IO流和数据库操作中常见 如果catch中有return语句，finally中的语句依然会被执行，且在return之前 try...catch的几种变形 try...catch...finally try...catch try...catch...catch try...catch...catch...finally try...finally（主要为了释放资源） 3. 自定义异常 继承自Exception public class MyException extends Exception{ public MyException(){} public MyException(String message){ super(message); } } public class Teacher{ public void checkScore(int score) throws MyException{ if(score &gt; 0 &amp;&amp; score &lt; 100){ System.out.println(&quot;分数正常&quot;); }else{ throw new MyException(&quot;分数不正常&quot;); } } } public class Demo{ public static void main(String [] args){ Teacher t = new Teacher(); t.checkScore(50); t.checkScore(120); } } 77. I/O流 1. File类（路径名的表示形式，文件是否存在还不一定） 包：java.io.File 构造方法： public File（File parent，String child）：创建在父路径对象（File parent）下的指定子文件（String child）的File对象 public File（String pathname）：创建一个绝对路径文件的File对象 public File（String parent，String child）：创建在父路径（String parent）下的指定子文件（String child）的File对象 public File（URI uri）：创建一个统一资源标识符表示的文件的File对象 常用方法： public boolean canExecute（）：测试文件是否具有可执行权限 public boolean canRead（）：测试文件是否具有可读权限 public boolean canWrite（）：测试文件是否具有可写权限 public int comparaTo（File pathname）： public boolean createNewFile（）：当且仅当前文件不存在时创建新文件 public boolean delete（）：删除File对象对应的文件或目录 public void deleteOnExit（）：在虚拟机终止时删除File对象对应的文件或目录 public boolean equals（Object obj）：比较是否相等 public boolean exists（）：测试该文件是否存在 public File getAbsoluteFile（）：返回File对象的绝对路径的File对象 public String getAbsolutePath（）：返回File对象的绝对路径的字符串 public String getCanonicalPath（）：返回File对象的规范化绝对路径，（带.和..的路径格式） public File getCanoncialFile（）：返回File对象的规范化表示File对象 public String getName（）：获取File对象表示的文件名或目录名 public String getParent（）：获取File对象父目录的字符串 public File getParentFile（）：获取File对象父目录的File对象 public String getPath（）：将定义File对象时的路径返回 public long getTotalSpace（）：获取File对象表示的文件所在的磁盘分区的总容量（单位：Byte） public long getFreeSpace（）：获取File对象表示的文件所在的磁盘分区的剩余容量（单位：Byte） public boolean isAbsolute（）：判断File对象的路径名是否为绝对路径 public boolean isDirectory（）：判断File对象所表示的文件是否为目录 public boolean isFile（）：判断File对象所表示的文件是否为文件 public boolean isHidden（）：判断File对象所表示的文件是否为隐藏文件 public long lastModified（）：获取File对象所表示的文件最后一次修改时间 public long length（）：获取File对象多表示的文件的大小（单位：Byte） public String[ ] list（）：将File对象表示的目录中所有文件名加入到字符串数组中，如果File对象不表示目录，返回null public String[ ] list（FilenameFilter filter）：将File对象表示的目录中所有文件名满足过滤器规则的加入到字符串数组中，如果File对象不表示目录，返回null FilenameFilter文件名过滤器 包：java.io.FilenameFilter（函数式接口） 方法：public boolean accept（File dir，String name） //匿名内部类写法 File file = new File(&quot;E:\\\\壁纸&quot;); String [] list = file.list(new FilenameFilter(){ @Override public boolean accept(File dir,String name){ return name.toLowerCase().endsWith(&quot;.png&quot;); } }); //Lambda表达式写法 File file = new File(&quot;E:\\\\壁纸&quot;); String [] list = file.list((File dir,String name) -&gt; { return name.toLowerCase().endsWith(&quot;.png&quot;); }); public File[ ] listFiles（）：将File对象表示的目录中所有文件加入到文件对象数组中，如果File对象不表示目录，返回null pubic File[ ] listFiles（FileFilter filter）：将File对象表示的目录中所有满足过滤器规则的文件加入到文件对象数组中，如果File对象不表示目录，返回null public File[ ] listFiles（FilenameFilter filter）：将File对象表示的目录中所有满足过滤器规则的文件加入到文件对象数组中，如果File对象不表示目录，返回null public static File[ ] listRoots（）：获取所有文件系统根目录，即盘符 File[] files = File.listRoots(); for (int i = 0; i &lt; files.length; i++) { System.out.println(files[i]); } //输出结果 C:\\ D:\\ E:\\ F:\\ public boolean mkdir（）：创建File对象对应的目录 public boolean mkdirs（）：创建File对象对应的目录，包括任何必须但不存在的父目录 public boolean renameTo（File dest）：重命名File对象所表示的文件 public Path toPath（）：将File对象转为Path对象（java.nio.file.Path） public String toString（）：将File对象转为字符串 public String toURI（）：将File对象转为URI对象 public URL toURL（）：将File对象转为URL对象，已弃用，建议使用URI.toURL（）方法 2. 递归 概述：方法定义中调用方法本身的现象 注意 递归一定要有出口，否则就是死递归 递归次数不能太多，否则栈内存溢出【递归次数(即方法调用次数)×方法栈帧 &gt; 栈大小 导致 栈溢出】 构造方法不能递归使用 例 //递归求阶乘 public static void main(String [] args){ factorical(5); } public static void factorial(int number){ if(number==1){ return 1; }else{ return number*factorical(number-1); } } //递归求不死神兔问题(斐波那契数列)1，1，2，3，5，8... public static void mian(String [] args){ fib(10); } public static int fib(int number){ if(number==1 || number==2){ return 1; }else{ return fib(number-1)+fib(number-2); } } //递归查找某路径下所有以指定后缀.mp4结尾的文件 public static void main(String [] args){ } public static void getAllFilePathMp4(File srcFolder){ File[] files = srcFolder.listFiles(); for(File f:files){ if(f.isDirectory()){ getAllFilePathMp4(f); }else{ if(f.getName().endsWith(&quot;.mp4&quot;)){ System.out.println(&quot;Name:&quot;+f.getName()+&quot;[&quot;+&quot;Path:&quot;+f.getAbsolutePath()+&quot;]&quot;); } } } } //递归删除指定目录下所有文件 public static void deleteAllFile(File srcFolder){ File[] files = srcFolder.listFiles(); if(files.length==0){ srcFolder.delete(); }else{ for(File f:files){ if(f.isDirectory()){ deleteAllFile(f); }else{ System.out.println(&quot;Name:&quot;+f.getName()+&quot;\\tDeleteState:&quot;+f.delete()); } } System.out.println(&quot;Name:&quot;+ srcFolder.getName()+&quot;\\tDeleteState:&quot;+srcFolder.delete()); } } 3. 字节流（万能的流） 3.1 字节输入流(java.io.InputStream抽象类) FileInputStream 包：java.io.FileInputStream 构造方法 public FileInputStream（File file） public FileInputStream（FileDescriptor fdObj） public FileInputStream（String name） 常用方法 public int available（） public void close（）：关闭输入流并释放资源 protected void finalize（） public FileChannel getChannel（） public FileDescriptor getFD（） public int read（） public int read（byte[ ] b）：从输入流中读取b.length字节的数据，返回读入b缓冲区的字节数，如果读取完毕返回-1，（一次读取多个字节数据，提高读写速度） public int read（byte[ ] b，int off，int len） public long skip（long n） 3.2 字节输出流(java.io.OutputStream抽象类) FileOutputStream 包：java.io.FileOutputStream 构造方法 public FileOutputStream（File file） public FileOutputStream（File file，boolean append） public FileOutputStream（FileDescriptor fdObj） public FileOutStream（String name） public FileOutputStream（String name，boolean append） 如果文件不存在，会自动创建，但如果目录不存在，不会自动创建，抛异常 常用方法： public void close（）：关闭输出流，并释放资源 protected void finalize（） public FileChannel getChannel（） public FileDescriptor getFD（） public void write（byte[ ] b） public void write（byte[ ] b，int off，int len） public void write（int b） 字符串是utf-8编码，一个汉字三个字节，一个字母一个字节。 字符串是gbk编码时，一个汉字两个字节，一个字母一个字节。 计算机如何判断将三个字节拼成一个汉字？ 文件的编码格式，以及单字节的格式 不同系统的换行符识别不同 \\r（Mac系统） \\n（Linux系统） \\r\\n（Windows系统） 3.3 字节缓冲流（提高FileInputStream的读写效率）装饰设计模式 BufferedInputStream 包：java.io.BufferedInputStream 构造方法： public BufferedInputStream（InputStream in） public BufferedInputStream（InputStream in，int size）：可以指定缓冲区byte数组大小 常用方法： public int read（） public int read（byte[ ] b，int off，int len）： public void reset（） public long skip（long n） BufferedOutputStream 包：java.io.BufferedOutputStream 构造方法： public BufferedOutputStream（OutputStream out） public BufferedOutputStream（OutputStream out，int size） 常用方法： public void flush（）：刷新缓冲输出流 public void write（byte [ ] b，int off，int len）：从指定字节数组写入len个字节 public void write（int b）：将指定字节写入缓冲输出流 读写文件效率比较 public class Demo5 { public static void main(String[] args) throws IOException { long startTime = System.currentTimeMillis(); // methodA(&quot;E:\\\\视频教程\\\\java\\\\day02\\\\day01\\\\视频\\\\1_2020-09-09.mp4&quot;,&quot;F:\\\\copy1.mp4&quot;);//半个小时，我吐了 // methodB(&quot;E:\\\\视频教程\\\\java\\\\day02\\\\day01\\\\视频\\\\1_2020-09-09.mp4&quot;,&quot;F:\\\\copy2.mp4&quot;);//共耗时：5246毫秒 // methodC(&quot;E:\\\\视频教程\\\\java\\\\day02\\\\day01\\\\视频\\\\1_2020-09-09.mp4&quot;,&quot;F:\\\\copy3.mp4&quot;);//共耗时：13230毫秒 // methodD(&quot;E:\\\\视频教程\\\\java\\\\day02\\\\day01\\\\视频\\\\1_2020-09-09.mp4&quot;,&quot;F:\\\\copy4.mp4&quot;);//共耗时：1389毫秒 long endTime = System.currentTimeMillis(); System.out.println(&quot;共耗时：&quot;+ (endTime-startTime) +&quot;毫秒&quot;); } public static void methodA(String srcFolder,String destFolder) throws IOException { FileInputStream fis = new FileInputStream(srcFolder); FileOutputStream fos = new FileOutputStream(destFolder); int flag = 0; while((flag=fis.read())!=-1){ fos.write(flag); } fos.close(); fis.close(); } public static void methodB(String srcFolder,String destFolder) throws IOException { FileInputStream fis = new FileInputStream(srcFolder); FileOutputStream fos = new FileOutputStream(destFolder); int flag = 0; byte [] b = new byte[1024]; while((flag=fis.read(b))!=-1){ fos.write(b); } fos.close(); fis.close(); } public static void methodC(String srcFolder,String destFolder) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFolder)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFolder)); int flag = 0; while((flag=bis.read())!=-1){ bos.write(flag); } bos.close(); bis.close(); } public static void methodD(String srcFolder,String destFolder) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFolder)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFolder)); int flag = 0; byte [] b = new byte[1024]; while((flag=bis.read(b))!=-1){ bos.write(b); } bos.close(); bis.close(); } } 4. 字符流（为了操作文本数据，Java提供了字符流，将字节流转换为字符流） 4.1 字符输入流(java.io.Reader) InputStreamReader 构造方法 public InputStreamReader（InputStream in）：使用默认字符集将字节输入流转为字符输入流 public InputStreamReader（InputStream in，Charset cs）：创建指定字符集将字节输入流转为字符输入流 public InputStreamReader（InputStream in，CharsetDecoder dec）：创建指定字符集解码器输入流 public InputStreamReader（InputStream in，String charsetName）：创建指定字符集输入流 常用方法 String(byte [ ] bytes，String charsetName)：通过指定字符集编码字节数组 byte[ ] getBytes(String charsetName)：使用指定字符集编码把字符串编码为字节数组 public void close（）：关闭输入流释放资源 public String getEncoding（）：返回字符集编码名称 public int read（）：读一个字符 public int read（char[ ] cbuf，int offset，int length）：将字符输入流读入数组的一部分 public boolean ready（）：告诉这个流是否准备好被读取 public void mark（int readAheadLimit）：标记流中当前位置 public boolean markSupport（）：告诉这个流是否支持mark操作 public int read（char[ ] cbuf）：将字符输入流读入数组 public int read（CharBuffer target）：将字符输入流读入指定字符缓冲区 public void reset（）：重置流 public long skip（long n）：跳过指定长度字符 4.2 字符输出流(java.io.Writer) OutputStreamWriter 构造方法： public OutputStreamWriter（OutputStream out）：使用默认字符集将字节输出流转为字符输出流 public outputStreamWriter（OutputStream out，Charset cs）：使用指定字符集解码将字节输出流转为字符输出流 public OutputStreamWriter（OutputStream out，CharsetEncoder enc）：创建指定字符集解码的字符输出流 public OutputStreamWriter（OutputStream out，Stirng charsetName）：创建指定名字编码解码的字符输出流 常用方法： public void close（）：关闭流，释放资源（会先刷新流，即调用flush（）方法） public void flush（）：刷新流 public String getEncoding（）：返回该字符输出流的编码格式 public void write（char[ ] cbuf，int off，int len）：写入字符数组的一部分 public void write（int c）：写入一个字符 public void write（String str，int off，int len）：写入一个字符串的一部分 public Writer append（char c）：追加指定字符 public Writer append（CharSequence csq）：追加指定字符序列 public Writer append（CharSequence csq，int start，int end）：追加指定字符序列子序列 public abstract void close（）：关闭流 public abstract void flush（）：刷新流 public void write（char[ ] cbuf）：写入一个字符数组 public void write（String str）：写入字符串 4.3 转换流简化写法 FileWriter = FileOutputStream + 字符编码 FileReader = FileInputStream + 字符编码 4.4 字符缓冲流 BufferedReader BufferedWriter 复制文本文件 复制图片 把ArrayList中的字符串数据存储到文本文件 从文本文件读取一行数据保存到集合中，并遍历 复制单级文件夹 复制多级文件夹 键盘输入几个学生信息，按照年龄排序，存入文本文件 加入a.txt文件中一串字符串“asdlfjsdaldjasf” 请编写代码实现读取内容，然后排序后写入b.txt 登录注册io版 5. 打印流（只有写数据的没有读数据的） 5.1 字节打印流（java.io.PrintStream） 5.2 字符打印流（java.io.PrintWriter） 5.3 特点： 只有写数据没有读取数据 可以操作任意类型的数据 如果启动了自动刷新，无需手动刷新 PrintWriter pw = new PrintWriter(new FileWriter(&quot;E:\\\\a.txt&quot;),true) 可以直接操作文本文件 6. 序列化流 序列化是指把一个Java对象变成二进制内容，本质上就是一个byte[]数组。 为什么要把Java对象序列化呢？ 因为序列化后可以把byte[]保存到文件中，或者把byte[]通过网络传输到远程，这样，就相当于把Java对象存储到文件或者通过网络传输出去了。 有序列化，就有反序列化，即把一个二进制内容（也就是byte[]数组）变回Java对象。有了反序列化，保存到文件中的byte[]数组又可以“变回”Java对象，或者从网络上读取byte[]并把它“变回”Java对象 6.1 ObjectOutputStream（序列化） 父类：OutputStream 包：java.io.ObjectOutputStream 构造方法： public ObjectOutputStream（OutputStream out） 常用方法： public void writeObject（Object obj）：写出对象 //类的序列化由实现java.io.Serializable接口的类启用。 不实现此接口的类将不会使任何状态序列化或反序列化。 可序列化类的所有子类型都是可序列化的。 序列化接口没有方法或字段，仅用于标识可串行化的语义。 public void close（）：关闭输出流 6.2 ObjectInputStream（反序列化） 父类：InputStream 包：java.io.ObjectInputStream 构造方法： public ObjectInputStream（InputStream in） 常用方法： public Object readObject（）：从ObjectOutputStream中读取对象 public void close（）：关闭输入流 例 package com.demo.javase; import java.io.Serializable; /** * @author 絷缘 * @version 1.0 * @date 2020/12/9 8:56 **/ public class Student implements Serializable { private String name; private int age; public Student(){} public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return &quot;Student{&quot; + &quot;name='&quot; + name + '\\'' + &quot;, age=&quot; + age + '}'; } } public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(&quot;F:\\\\b.txt&quot;)); oos.writeObject(new Student(&quot;zhiyuan&quot;,22)); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(&quot;F:\\\\b.txt&quot;)); Object o = ois.readObject(); if(o instanceof Student){ System.out.println(o); } } java.io.InvalidClassException： Exception in thread &quot;main&quot; java.io.InvalidClassException: com.demo.javase.Student; local class incompatible: stream classdesc serialVersionUID = -2157397684930231136, local class serialVersionUID = 1206998284855657234 若在序列化对象时，类未被修改，序列化后，手动修改了类文件，那么进行反序列化时会有异常 可序列化的类的版本号serialVersionUID不同，抛出InvalidClassException 若在序列化对象时，类没有无参构造方法，序列化后，进行反序列化时会有异常 若在序列化对象时，类中有未知的数据类型，序列恶化后，进行反序列化时会有异常 serialVersionUID和transient serialVersionUID（Serializable的底层比较方式） 官方手册：强烈建议声明可序列化类的版本号 private static final long serialVersionUID = 25L; 声明此版本号后，手动修改类文件后，反序列化未发生错误 transient（类中成员不想被序列化和反序列化） 被transient修饰的成员，不参与序列化与反序列化，被反序列化后依据成员本身类型给默认值 7. 特殊操作流 7.1 Properties 父类：java.util.Hashtable&lt;Object，Object&gt; 包：java.util.Properties Properties是一个Map体系的集合类，可以直接保存到流中或从流中加载 构造方法： public Properties（） public Properties（Properties defaults） 常用方法： public String getProperty（String key）：根据指定key，获取value public String getProperty（String key，String defaultValue） public void list（PrintStream out）：把属性列表打印到指定输出流 public void list（PrintWriter out）：把属性列表打印到输出流 public void load（InputStream in）：从输入字节流读取属性列表 public void load（Reader reader）： public void loadFromXML（InputStream in）：从指定XML文件输入流中读取所有属性加入properties中 public Enumeration&lt;?&gt; propertiesNames（）：返回属性表的所有key的枚举 public Object setProperty（String key，String value）：调用HashTable的put（）方法，向属性集合中加入数据 public void store（OutputStream out，String comments） public void store（Writer writer，String comments） public void storeToXML（OutputStream os，String comment） public void storeToXML（OutputStream os，Stirng comment，Stirng encoding） public Set stringPropertyNames（）：键的集合 public static void main(String[] args) throws IOException { Properties pt = new Properties(); pt.setProperty(&quot;username&quot;,&quot;root&quot;); pt.setProperty(&quot;password&quot;,&quot;123456&quot;); Set&lt;String&gt; keys = pt.stringPropertyNames(); Iterator&lt;String&gt; it = keys.iterator(); for(;it.hasNext();){ System.out.println(it.next()); } pt.store(new BufferedWriter(new FileWriter(&quot;F:\\\\c.txt&quot;)),null); Properties pt2 = new Properties(); pt2.load(new BufferedReader(new FileReader(&quot;F:\\\\c.txt&quot;))); for (String key:pt2.stringPropertyNames()) { System.out.println(key+&quot;=&quot;+pt2.getProperty(key)); } } 78. 多线程 1. 进程：系统资源分配的最小单位 2. 线程：是进程的执行单元，是CPU调度的最小单位 单线程 多线程 3. 多线程实现方式 3.1 继承Thread类（java.lang.Thread） 继承Thread类并重写run方法，使用start方法启动线程 public static void main(String[] args) { MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); mt1.start(); mt2.start(); } public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i &lt; 1000; i++) { System.out.print(i+&quot;\\t&quot;); } } } 设置和获取线程名称 //第一种，使用setName设置线程名称 public static void main(String[] args) { MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); mt1.setName(&quot;线程A&quot;); mt2.setName(&quot;线程B&quot;); mt1.start(); mt2.start(); } public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i &lt; 1000; i++) { System.out.print(getName()+&quot;:&quot;+i+&quot;\\t&quot;); } } } //第二种，使用父类带参构造设置线程名称 public static void main(String[] args) { MyThread mt1 = new MyThread(&quot;线程A&quot;); MyThread mt2 = new MyThread(&quot;线程B&quot;); mt1.start(); mt2.start(); } public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i &lt; 1000; i++) { System.out.print(getName()+&quot;:&quot;+i+&quot;\\t&quot;); } } public MyThread(String name){ super(name); } } 获取启动线程的线程名 String name = Thread.currentThread().getName(); System.out.println(name); 线程调度 分时调度模型 抢占式调度模型 public final int getPriority()：返回此线程的优先级 public final void setPriority()：更改线程优先级 Thread.MAX_PRIORITY=10 Thread.MIN_PRIORITY=1 Thread.NORM_PRIORITY=5 public static void main(String[] args) { ThreadPriority tp1 = new ThreadPriority(&quot;线程A&quot;); ThreadPriority tp2 = new ThreadPriority(&quot;线程B&quot;); tp1.setPriority(5); tp2.setPriority(6); tp1.start(); tp2.start(); } public class ThreadPriority extends Thread{ @Override public void run() { for (int i = 0; i &lt; 100; i++) { System.out.println(getName()+&quot;:&quot;+i); } } public ThreadPriority(String name){ super(name); } } 线程控制 public static sleep(long millis)：使当前正在执行的线程停留指定毫秒数 public void join()：等待这个线程死亡 public void setDaemon(boolean on)：将此线程标记为守护线程，当运行的线程都是守护线程时，Java虚拟机将退出 public static void yield()：使当前占用资源的线程主动让出资源 //sleep() public static void main(String[] args) { ThreadSleep ts1 = new ThreadSleep(); ThreadSleep ts2 = new ThreadSleep(); ts1.start(); ts2.start(); } public class ThreadSleep extends Thread{ @Override public void run() { for (int i = 0; i &lt; 100; i++) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } //join() public static void main(String[] args) { ThreadJoin tj1 = new ThreadJoin(&quot;A&quot;); ThreadJoin tj2 = new ThreadJoin(&quot;B&quot;); tj1.start(); try { tj1.join(); } catch (InterruptedException e) { e.printStackTrace(); } tj2.start(); } public class ThreadJoin extends Thread { @Override public void run() { for (int i = 0; i &lt; 1000; i++) { System.out.print(getName()+&quot;:&quot;+i+&quot;\\t&quot;); } } public ThreadJoin(String name){ super(name); } } //setDaemon() public static void main(String[] args) { ThreadDaemon td1 = new ThreadDaemon(&quot;A&quot;); ThreadDaemon td2 = new ThreadDaemon(&quot;B&quot;); Thread.currentThread().setName(&quot;C&quot;); td1.setDaemon(true); td2.setDaemon(true); td1.start(); td2.start(); for (int i = 0; i &lt; 100; i++) { System.out.println(Thread.currentThread().getName()+&quot;:&quot;+i); } } public class ThreadDaemon extends Thread{ @Override public void run() { for (int i = 0; i &lt; 100; i++) { System.out.println(getName()+&quot;:&quot;+i); } } public ThreadDaemon(String name){ super(name); } } 线程生命周期 创建线程对象 线程就绪等待分配资源 分配到资源开始运行 线程结束 3.2 实现Runnable接口（java.lang.Runnable） 定义一个类实现Runnable接口 重写run方法 创建该类对象，并将对象作为参数构造Thread对象 启动线程 public class RunnableDemo1 implements Runnable{ @Override public void run() { for (int i = 0; i &lt; 100; i++) { System.out.println(Thread.currentThread().getName()+&quot;:&quot;+i); } } public RunnableDemo1(String s){ Thread.currentThread().setName(s); } } public class Demo6 { public static void main(String[] args) { Thread t1 = new Thread(new RunnableDemo1()); Thread t2 = new Thread(new RunnableDemo1()); Thread t3 = new Thread(new RunnableDemo1(), &quot;线程C&quot;); t1.start(); t2.start(); t3.start(); } } 推荐使用实现Runnable接口的方式实现多线程 原因： Java中只能单继承，所以放我们继承Thread类实现多线程后，想要继续继承其他类就不可能了 实现Runnable接口的多继承可以实现资源共享 3.3 实现Callable接口（java.util.concurrent.Callable） 定义一个类实现Callable接口 重写call方法 创建该类对象，启动线程 public class MyCallableDemo implements Callable { @Override public Object call() throws Exception { for (int i = 0; i &lt; 100; i++) { System.out.println(Thread.currentThread().getName()+&quot;-&quot;+i); } return null; } } public class Demo7 { public static void main(String[] args) { MyCallableDemo myCallableDemo = new MyCallableDemo(); ExecutorService service = Executors.newFixedThreadPool(5); service.submit(myCallableDemo); } } 3.4 匿名内部类实现多线程 public void ThreadDemo(){ Thread t1 = new Thread(){ @Override public void run() { for (int i = 0; i &lt; 100; i++) { System.out.println(Thread.currentThread().getName()+&quot;-&quot;+i); } } }; t1.start(); } public void RunnableDemo(){ Runnable r1 = new Runnable() { @Override public void run() { for (int i = 0; i &lt; 100; i++) { System.out.println(Thread.currentThread().getName()+&quot;-&quot;+i); } } }; new Thread(r1,&quot;线程A&quot;).start(); new Thread(r1,&quot;线程B&quot;).start(); } 4. 线程同步 //卖票问题 public class SellTicket implements Runnable { private int tickets = 100; @Override public void run() { while(true){ if (tickets &gt; 0) { System.out.println(Thread.currentThread().getName()+&quot;正在出售第&quot;+tickets+&quot;票&quot;); tickets--; }else{ break; } } } } public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, &quot;窗口1&quot;); Thread t2 = new Thread(st, &quot;窗口2&quot;); Thread t3 = new Thread(st, &quot;窗口3&quot;); t1.start(); t2.start(); t3.start(); } } 4.1 出现了问题（多线程数据安全问题） 相同的票出现了多次 资源被其他线程抢占，其他线程资源又被其他线程抢占 出现了负数票数 当某个线程抢到CPU执行权，而且正在出售第1张票，票数减一变为0，而此时其他线程抢到了执行权，票数减一变为-1 4.2 解决办法 同步代码块：锁定多条语句操作共享数据的代码 优点：解决了多线程的数据安全问题 缺点：当线程较多的时候，每个线程都回去判断同步上的锁，耗费资源，降低运行效率 synchronized(任意对象){ 多条语句操作共享数据的代码 } synchronized(任意对象)：相当于给代码加锁了，任意对象就可以看成是一把锁 public class SellTicket implements Runnable { private int tickets = 100; private Object object = new Object(); @Override public void run() { while(true){ synchronized(object){ if (tickets &gt; 0) { System.out.println(Thread.currentThread().getName()+&quot;正在出售第&quot;+tickets+&quot;票&quot;); tickets--; }else{ break; } } } } } public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, &quot;窗口1&quot;); Thread t2 = new Thread(st, &quot;窗口2&quot;); Thread t3 = new Thread(st, &quot;窗口3&quot;); t1.start(); t2.start(); t3.start(); } } 同步方法：把synchronized关键字加到方法上 同步方法的锁对象：this public class SellTicket implements Runnable { private int tickets = 100; private Object object = new Object(); private int i = 0; @Override public void run() { while(true){ if(i%2==0){ synchronized(this){ if (tickets &gt; 0) { System.out.println(Thread.currentThread().getName()+&quot;正在出售第&quot;+tickets+&quot;票&quot;); tickets--; }else{ break; } } i++; }else{ sellTicket(); } } } public synchronized void sellTicket(){ if (tickets &gt; 0) { System.out.println(Thread.currentThread().getName()+&quot;正在出售第&quot;+tickets+&quot;票&quot;); tickets--; } } } public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, &quot;窗口1&quot;); Thread t2 = new Thread(st, &quot;窗口2&quot;); Thread t3 = new Thread(st, &quot;窗口3&quot;); t1.start(); t2.start(); t3.start(); } } 同步静态方法：在static后加上synchronized关键字 同步静态方法的锁对象：类名.class（反射） public class SellTicket implements Runnable { private static int tickets = 100; private Object object = new Object(); private int i = 0; @Override public void run() { while(true){ if(i%2==0){ synchronized(SellTicket.class){ if (tickets &gt; 0) { System.out.println(Thread.currentThread().getName()+&quot;正在出售第&quot;+tickets+&quot;票&quot;); tickets--; }else{ break; } } i++; }else{ sellTicket(); } } } public static synchronized void sellTicket(){ if (tickets &gt; 0) { System.out.println(Thread.currentThread().getName()+&quot;正在出售第&quot;+tickets+&quot;票&quot;); tickets--; } } } public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, &quot;窗口1&quot;); Thread t2 = new Thread(st, &quot;窗口2&quot;); Thread t3 = new Thread(st, &quot;窗口3&quot;); t1.start(); t2.start(); t3.start(); } } 5. 线程安全的类 5.1 StringBuffer(在多线程中会被使用到) 5.2 Vector（不常被使用） 5.3 Hashtable（不常被使用） 常使用Collections.synchronizedList（new ArrayList()）将线程不安全的集合类变成线程安全的集合类 6. 锁Lock（JDK1.5） Lock实现同步比使用synchronized方法和语句可以获得更广泛的锁定操作 常用方法： public void lock（）：获得锁 public void unlock（）：释放锁 实现类：ReentrantLock 构造方法： public ReentrantLock（） 实现 public class SellTicket implements Runnable { private static int tickets = 100; private Object object = new Object(); private int i = 0; private Lock lock = new ReentrantLock(); @Override public void run() { while (true) { try{ lock.lock(); if (tickets &gt; 0) { System.out.println(Thread.currentThread().getName() + &quot;正在出售第&quot; + tickets + &quot;票&quot;); tickets--; } else { break; } }finally{ lock.unlock(); } } } } public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, &quot;窗口1&quot;); Thread t2 = new Thread(st, &quot;窗口2&quot;); Thread t3 = new Thread(st, &quot;窗口3&quot;); t1.start(); t2.start(); t3.start(); } } 7. 死锁问题 7.1 同步弊端 效率低 如果出现了同步嵌套，容易出现死锁 7.2 死锁的产生 概述：死锁或两个以上的进程在执行过程中，由于竞争资源或者由于彼此通信而造成的一种阻塞的现象，若无外力作用，它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁，这些永远在互相等待的进程称为死锁进程。 产生：我们在解决多线程共享资源的线程同步问题时，会使用synchronized关键字修饰方法或者通过Lock加锁方式修饰方法、代码块，防止多个线程访问统一资源产生的线程安全问题。但是当线程X持有锁A，等待锁B，而线程Y此时持有锁B，等待锁A时，就会出现X，Y两个线程互相等待的情况，这种情况就是死锁。 产生条件 互斥条件：一个资源每次只能被一个进程使用。 保持和请求条件：一个进程因请求资源而阻塞时，对已获得资源保持不放。 不可剥夺条件：进程已获得资源，在未使用完成前，不能被剥夺。 循环等待条件：若干进程之间形成一种头尾相接的循环等待资源关系。 7.3 死锁的解决 只要破坏其中任意一个条件，就可以避免死锁，其中最简单的就是破环循环等待条件。按同一顺序访问对象，加载锁，释放锁。 8. 线程间的通信（等待唤醒机制） 8.1 概述 多个线程在处理同一个资源，但是处理的任务不相同 8.2 等待唤醒机制 多个线程间的⼀种协作机制。谈到线程我们经常想到的是线程间的竞争( race )，比如去争夺锁，但这并不是故事的全部，线程间也会有协作机制。就好比在公司你你和你的同事，你们可能存在在晋升时的竞争，但更多时候你们更多是⼀起合作以完成某些任务。 就是在⼀个线程进行了规定操作后，就进⼊等待状态( wait（）)，等待其他线程执行完他们的指定代码过后再将其唤醒( notify（） )；在有多个线程进⾏等待时，如果需要，可以使⽤ notifyAll（）来唤醒所有的等待线程。 wait/notify 就是线程间的⼀种协作机制。 常用方法： wait（）：线程不再活动，不再参与调度，进入wait set中，因此不会浪费CPU资源，也不会去竞争锁，此时线程状态为WAITING，等待其他线程完成指定任务，使用notify通知当前线程从wait set中释放出来，重新进入调度队列 notify（）：通知处在wait set中的线程释放进入调度队列 notifyAll（）：通知所有处在wait set中的线程释放进入调度队列 注意： 哪怕只通知了⼀个等待的线程，被通知线程也不能立即恢复执行，因为它当初中断的地方是在同步块内，而此刻它已经不持有锁， 所以它需要再次尝试去获取锁（很可能面临其它线程的竞争），成功后才能在当初调⽤ wait 方法之后的地方恢复执行。 总结如下： 如果能获取锁，线程就从 WAITING 状态变成 RUNNABLE 状态； 否则，从 wait set 出来，又进⼊ entry set，线程就从WAITING状态⼜变成BLOCKED 状态。 注意事项 wait方法与notify方法必须要由同⼀个锁对象调用。因为：对应的锁对象可以通过notify唤 醒使用同⼀个锁对象调用的wait方法后的线程。 wait方法与notify方法是属于Object类的方法的。因为：锁对象可以是任意对象，而任意对 象的所属类都是继承了Object类的。 wait方法与notify方法必须要在同步代码块或者是同步函数中使⽤。因为：必须要通过锁对 象调用这2个方法。 8.3 生产者消费者问题 代码实现 public class BaoZi { private String pi; private String xian; private boolean flag = false; public String getPi() { return pi; } public String getXian() { return xian; } public boolean isFlag() { return flag; } public void setPi(String pi) { this.pi = pi; } public void setXian(String xian) { this.xian = xian; } public void setFlag(boolean flag) { this.flag = flag; } } public class BaoZiPu implements Runnable{ private BaoZi bz; public BaoZiPu(BaoZi bz) { this.bz = bz; } @Override public void run() { int count = 0; while(true){ synchronized (bz){ if(bz.isFlag()){ try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ if(count%2==0){ bz.setPi(&quot;薄皮&quot;); bz.setXian(&quot;猪肉大葱&quot;); }else{ bz.setPi(&quot;冰皮&quot;); bz.setXian(&quot;韭菜鸡蛋&quot;); } count++; System.out.println(&quot;包子铺正在做：&quot;+bz.getPi()+bz.getXian()+&quot;包子&quot;); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } bz.setFlag(true); bz.notify(); System.out.println(&quot;包子已经做好了：&quot;+bz.getPi()+bz.getXian()+&quot;包子，大家可以来买了&quot;); } } } } } public class ChiHuo implements Runnable{ private BaoZi bz; public ChiHuo(BaoZi bz) { this.bz = bz; } @Override public void run() { while(true){ synchronized (bz){ if(!bz.isFlag()){ try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(&quot;吃货正在吃：&quot;+bz.getPi()+bz.getXian()+&quot;的包子&quot;); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } bz.setFlag(false); bz.notify(); System.out.println(&quot;吃货已经把：&quot;+bz.getPi()+bz.getXian()+&quot;的包子吃完了&quot;); System.out.println(&quot;===================================================&quot;); } } } } } public class Demo { public static void main(String[] args) { BaoZi bz = new BaoZi(); new Thread(new BaoZiPu(bz)).start(); new Thread(new ChiHuo(bz)).start(); } } 9. 线程池 概述：我们使用线程时如果并发的线程数量很多，并且每个线程都是执行一个很短的任务就结束了，这样频繁地创建线程就会大大降低系统的效率 线程池：就是一个容纳多个线程的容器，其中的线程可以反复使用，省去了频繁创建线程对象的操作，以避免消耗过多的资源 原理：集合容器，队列思想 代码实现： 包：java.util.concurrent.Executors：线程池的工厂类，用来生成线程池 静态方法： public static ExecutorService newFixedThreadPool（int nThreads）：创建一个可重用的线程数的线程池 参数：int nThread（创建线程池中包含的线程数量） 返回值：ExecutorService接口，返回ExecutorService接口的实现类对象 java.util.concurrent.ExecutorService：线程池接口 submit（Runnable task）：提交一个Runnable任务用于执行 void shutdown（） ：关闭销毁线程池的方法 线程池使用步骤 使用线程池的工厂类Executors里提供的静态方法newFixedThreadPool生成一个指定数量的线程池 创建一个类，实现Runnable接口，重写run方法，设置线程任务 调用ExecutorService中的静态方法submit，传递线程任务（实现类），开启线程，执行run方法 调用ExecutorService中的方法shutdown销毁线程池（不建议执行） public class ThreadPoolDemo{ public static void main(String [] args){ //1.使用线程池的工厂类Executors里提供的静态方法newFixedThreadPool生成一个指定数量的线程池 ExecutorService es = Executors.newFixedThreadPool(2); //2.创建一个类，实现Runnable接口，重写run方法，设置线程任务 //3.调用ExecutorService中的静态方法submit，传递线程任务（实现类），开启线程，执行run方法 es.submit(new MyThread())); es.submit(new MyThread())); es.submit(new MyThread())); es.shutdown(); es.submit(new MyThread()));//抛异常，线程池已经被销毁了，不能再获取线程了 } } public class MyThread implements Runnable{ @Override public void run(){ System.out.println(Thread.currentThread().getName()+&quot;创建了一个新线程&quot;); } } 10. 线程组 可以把线程归属到某一个线程组中，线程组中可以有线程对象，也可以有线程组，组中还可以有线程，这样的组织结构有点类似于树的形式 线程组的作用是：可以批量管理线程或线程组对象，有效地对线程或线程组对象进行组织 包：java.lang.ThreadGroup 构造方法 ThreadGroup（String name） ThreadGroup（ThreadGroup parent，String name） 代码实现 public class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } } public class ThreadGroupDemo { public static void main(String[] args) { ThreadGroup g1 = new ThreadGroup(&quot;线程组1&quot;); Thread t1 = new Thread(g1, new MyThread(), &quot;a&quot;); Thread t2 = new Thread(g1, new MyThread(), &quot;b&quot;); System.out.println(t1.getThreadGroup()); System.out.println(t2.getThreadGroup()); } } 多线程实现文件上传 多线程实现文件下载 11. 定时器 线程工具，用来调度多个定时任务，以后台线程的方式执行 11.1 类：java.util.Timer 和 java.util.TimerTask java.util.Timer 线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行，或定期重复执行。 方法 void schedule（TimeTasj task，Data time） void cancle（） java.util.TimerTask 方法 boolean cancle（） abstract void run（） 12. 小结 多线程的实现方式有几种 同步有几种方式 启动线程的方式 sleep和wait方法的区别 为什么wait和notify方法在Object中 线程的生命周期 练习：多线程网络编程群聊 79. 网络编程 1. 计算机网络 计算机之间为了实现资源共享和信息传递，通过通信线路连接起来的若干计算机组成的网络 局域网 城域网 广域网 2. 网络模型（OSI） OSI（Open System Interconnection开放式系统互联）参考模型（理论模型，但实际上没有人用这个标准） 物理层：负责为设备之间的数据通信提供传输信号和物理介质 数据链路层：负责通过协议保证传输数据的正确性MAC 网络层：负责定义能够表示所有网络节点的逻辑地址IP地址 传输层：负责是否选择差错恢复协议、数据流重用、错误顺序重排 会话层：负责使应用建立和维持会话，使通信在失效时继续恢复通信 表示层：负责定义转换数据格式及加密 应用层：负责文件访问和管理、可靠传输服务、远程操作服务（HTTP、FTP、SMTP） TCP/IP模型 网络接口层 网络层 传输层 应用层 3. 通信协议 TCP协议（Transmission Control Protocol传输控制协议）：三次握手，四次挥手 UDP协议（User Datagram Protocol用户数据报协议）：无连接的传输协议，不可靠的信息传输服务，每个包最大64KB IP协议（Internet Protocol互联网协议/网际协议）：负责数据从一台机器发送到另一台机器，给互联网每台设备分配一个唯一的标识IP地址 IPv4（4字节32位二进制标识地址）：4段8位二进制数 IPv6（16字节128位二进制标识地址）：8段十六进制数 4. IP地址与端口号 IP地址：用于在网络中唯一标识一台主机的一段地址（主机） 端口号：用于在通信实体上进行网络通讯应用程序的标识（进程） 常用端口号： MySQL：3306 Oracle：1521 Tomcat：8080 HTTP：80 FTP：21 SMTP：25 用户端口（1024以后的端口） 5. Java网络编程 5.1 InetAddress 包：java.net.InetAddress 子类： Inet4Address Inet6Address 无构造方法 常用方法： public static InetAddress[ ] getAllByName（String host）：根据指定主机名称的配置返回InetAddress对象数组 public static InetAddress getByAddress（byte[ ] addr）：根据指定IP地址字节数组返回InetAddress对象 public static InetAddress getByAddress（String host，byte[ ] addr）：根据指定主机名称和IP地址字节数组返回InetAddress对象 public boolean equals（Object obj）：比较两个对象 public byte[ ] getAddress（）：返回InetAddress对象的原始IP地址 public static InetAddress getLocalHost（）：获取本机主机InetAddress对象 public String getHostAddress（）：获取IP地址字符串 public String getHostName（）：获取IP地址对应主机名 public boolean isReachable（int timeout）：测试改地址是否可达，参数单位毫秒 public String toString（）：将IP地址转为String 5.2 Socket编程 socket（套接字）是网络中的一个通信节点：网络中通信端点的抽象 客户端Socket 服务器ServerSocket 通信要求：IP地址+端口号 开发步骤 服务器端： 创建ServerSocket，指定端口号 调用accept等待客户端接入 使用输入流，接收请求数据到服务器 使用输出流，发送响应数据给客户端 释放资源 客户端： 创建Socket，指定服务器IP地址+端口号 使用输出流，发送请求数据给服务器 使用输入流，接受相应数据到客户端 释放资源 ServerSocket类 包：java.net.ServerSocket 子类：java.net.SSLServerSocket 构造方法： public ServerSocket（）：创建未绑定的服务器套接字 public ServerSocket（int port）：创建绑定到指定端口号的服务器套接字 public ServerSocket（int port，int backlog）：创建服务器套接字并绑定到指定本地端口号和backlog public ServerSocket（int port，int backlog，InetAddress bindAddr）：创建绑定指定端口号的套接字，backlog和本地IP地址绑定 常用方法： public Socket accept（）：监听客户端数据，并返回客户端的Socket public void bind（SocketAddress endpoint）：将ServerSocket绑定到特定IP地址和端口号 public void bind（SocketAddress endpoint，int backlog） public void close（）：关闭套接字 public ServerSocketChannel getChannel（） public InetAddress getInetAddress（）：返回ServerSocket的本地地址 public int getLocalPort（）：返回ServerSocket正在使用的端口号 public SocketAddress getLocalSocketAddress（） 案例1：TCP编程实现客户端发送数据给服务器端 package com.demo.javase; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Date; /** * 基于TCp协议的服务端开发 * ①.创建ServerSocket，并绑定端口号 * ②.调用accept（）接收客户端请求 * ③.获取输入流，读取客户端发送数据 * ④.获取输出流，发送数据给客户端 * @author 絷缘 * @version 1.0 * @date 2020/12/8 21:46 **/ public class TCPServer { public static void main(String[] args) throws IOException { System.out.println(&quot;服务器已启动......&quot;); //①.创建ServerSocket，并绑定端口号 ServerSocket listener = new ServerSocket(8890); //②.调用accept（）接收客户端请求，阻塞方法，如果没有客户端请求，则阻塞 Socket socket = listener.accept(); InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String data = br.readLine(); System.out.println(&quot;客户端IP：&quot; + socket.getInetAddress() + &quot;\\t发送数据：&quot; + data + &quot;\\t时间：&quot; + new SimpleDateFormat(&quot;yyyy-MM-dd kk:mm:ss&quot;).format(new Date(System.currentTimeMillis()))); br.close(); socket.close(); listener.close(); } } package com.demo.javase; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.nio.charset.StandardCharsets; /** * 基于TCP的客户端开发 * ①.创建客户端套接字，并指定服务器的IP地址和端口号 * ②.获取输出流，发送数据给服务器 * ③.获取输入流，接收服务器返回数据 * ④.关闭，释放资源 * @author 絷缘 * @version 1.0 * @date 2020/12/8 22:04 **/ public class TCPClient { public static void main(String[] args) throws IOException { System.out.println(&quot;客户端已启动......&quot;); //①.创建客户端套接字，并指定服务器的IP地址和端口号 Socket socket = new Socket(&quot;127.0.0.1&quot;,8890); //②.获取输出流，发送数据给服务器 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); bw.write(&quot;Are you OK ?&quot;); bw.close(); socket.close(); } } 5.3 UDP协议发送接收数据 步骤 创建发送端的Socket对象 创建数据，把数据打包 调用Socket对象的方法发送数据 释放资源 创建接收端的Socket对象 创建接收数据包容器 接收数据并解析数据 释放资源 public class UDPSend { public static void main(String[] args) throws IOException { DatagramSocket ds = new DatagramSocket(); byte[] bytes = &quot;你好！&quot;.getBytes(); DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),8090); ds.send(dp); } } public class UDPReceive { public static void main(String[] args) throws IOException { //创建接收端Socket DatagramSocket ds = new DatagramSocket(8090); //创建一个数据包，作为接收容器 byte [] bys = new byte[1024]; DatagramPacket dp = new DatagramPacket(bys,bys.length); ds.receive(dp); //接收数据，解析数据并打印到控制台 byte[] data = dp.getData(); String str = new String(data,0,data.length); System.out.println(new StringBuffer().append(&quot;发送方IP：&quot;).append(dp.getAddress()).append(&quot;\\t发送内容：&quot;).append(str).toString()); //关闭资源 } } 5.4 TCP协议发送接收数据 创建客户端的Socket对象（Socket） 获取输出流对象，写数据 释放资源 创建服务器端的Socket对象（ServerSocket） 获取输入流对象，读数据 释放资源 public class TCPClient { public static void main(String[] args) throws IOException { System.out.println(&quot;客户端已启动......&quot;); //①.创建客户端套接字，并指定服务器的IP地址和端口号 Socket socket = new Socket(&quot;127.0.0.1&quot;,8890); //②.获取输出流，发送数据给服务器 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); bw.write(&quot;Are you OK ?&quot;); bw.close(); socket.close(); } } public class TCPServer { public static void main(String[] args) throws IOException { System.out.println(&quot;服务器已启动......&quot;); //①.创建ServerSocket，并绑定端口号 ServerSocket listener = new ServerSocket(8890); //②.调用accept（）接收客户端请求，阻塞方法，如果没有客户端请求，则阻塞 Socket socket = listener.accept(); InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String data = br.readLine(); System.out.println(&quot;客户端IP：&quot; + socket.getInetAddress() + &quot;\\t发送数据：&quot; + data + &quot;\\t时间：&quot; + new SimpleDateFormat(&quot;yyyy-MM-dd kk:mm:ss&quot;).format(new Date(System.currentTimeMillis()))); br.close(); socket.close(); listener.close(); } } 服务器可以反馈 public class TCPServer { public static void main(String[] args) throws IOException { System.out.println(&quot;服务器已启动......&quot;); //①.创建ServerSocket，并绑定端口号 ServerSocket listener = new ServerSocket(8890); //②.调用accept（）接收客户端请求，阻塞方法，如果没有客户端请求，则阻塞 Socket socket = listener.accept(); InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String data = br.readLine(); System.out.println(&quot;客户端IP：&quot; + socket.getInetAddress() + &quot;\\t发送数据：&quot; + data + &quot;\\t时间：&quot; + new SimpleDateFormat(&quot;yyyy-MM-dd kk:mm:ss&quot;).format(new Date(System.currentTimeMillis()))); //给反馈 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,StandardCharsets.UTF_8)); bw.write(&quot;Hello!Thank you!Thank you very much!&quot;); bw.newLine(); bw.flush(); socket.close(); listener.close(); } } public class TCPClient { public static void main(String[] args) throws IOException { System.out.println(&quot;客户端已启动......&quot;); //①.创建客户端套接字，并指定服务器的IP地址和端口号 Socket socket = new Socket(&quot;127.0.0.1&quot;,8890); //②.获取输出流，发送数据给服务器 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); bw.write(&quot;Are you OK ?&quot;); bw.newLine(); bw.flush(); //接受反馈 InputStream inputStream = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String str = br.readLine(); System.out.println(&quot;服务器：&quot; + str); bw.close(); br.close(); socket.close(); } } //在此处我犯了一个严重的错误，如果使用BufferedWriter 不加newLine()和flush()的话，服务端BufferedReader的readline()方法就读取不到msg,会一直阻塞下去。 6. 网络编程模型 6.1 BIO网络编程模型（同步阻塞的BIO）【Blocking I/O】 JDK1.4之前，我们使用的都是BIO，我们想要实现ServerSocket和Socket之间的通信，需要使用多线程来处理大量请求 同步阻塞I/O，服务器实现模式为一个连接一个线程，即客户端有连接请求时服务器就需要启动一个线程进行处理，如果这个连接不做任何事情会造成不必要的线程开销，可以通过线程池机制来改善。BIO方式适用于连接数目比较小且固定的架构，这种方式对服务端资源要求比较高，并发局限于应用中，在jdk1.4以前是唯一的io现在，但程序直观简单易理解 6.2 NIO网络编程模型（同步非阻塞NIO）【Non-blocking I/O】 NIO是基于事件驱动思想来完成的 同步非阻塞I/O，服务器实现模式为一个请求一个线程，即客户端发送的连接请求都会注册到多路复用器上，多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短（轻操作）的架构，比如聊天服务器，并发局限于应用中，编程比较复杂，jdk1,4开始支持 6.3 AIO网络编程模型（异步非阻塞AIO） 异步非阻塞I/O，服务器实现模式为一个有效请求一个线程，客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。AIO方式适用于连接数目多且连接比较长（重操作）的架构，比如相册服务器，充分调用OS参与并发操作，编程比较复杂，jdk1.7开始支持。 7. NIO 参考文档：https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html 7.1 NIO与传统IO的区别 NIO简介 Java NIO（New IO/Non-Blocking IO）是从Java1.4版本开始引入的一个新的IO，为了替代传统的阻塞式IO，NIO与原来的IO有同样的作用和目的，但是使用的方式完全不同，NIO支持面向缓冲区的、基于通道的IO操作，NIO将以更加高效的方式进行文件的读写操作。 区别 传统的IO是面向流的，NIO是面向缓冲区的 传统的IO是阻塞式IO，NIO是非阻塞式的 传统的IO没有选择器，NIO有选择器 7.2 通道（Channel）和缓冲区（Buffer） 概述： Java NIO系统的核心在于：通道（Channel）和缓冲区（Buffer），通道用于建立起到IO设备的连接（例如：文件、套接字），所以如果需要使用NIO时，首先就要获取一个连接到IO设备的通道，然后通过与缓冲区结合，对数据进行读写处理 简而言之：Channel负责传输，Buffer负责存储 7.3 缓冲区（Buffer） java.nio.Buffer：抽象类 概述：负责数据存取，缓存区底层就是数组 根据数据类型的不同，提供了相应的缓冲区（Boolean除外） ByteBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer CharBuffer 四个重要属性： Capacity：容量 Position：位置 Limit：上限 Mark：标记 常用方法： public int capacity（）：返回缓冲区容量 public Buffer clear（）：清除此缓冲区 public Buffer flip（）：反转这个缓冲区，由写变读，由读变写 public boolean hasRemaining（）：当前位置position到上限limit是否存在元素 public int limit（）：返回缓冲区的上限 public Buffer limit（int newLimit）：设置此缓冲区的上限 public Buffer mark（）：设置此缓冲区的标记 public int position（）：返回此缓冲区的位置 public Buffer position（int newPosition）：设置此缓冲区的位置 public int remaining（）：返回当前位置和上限之间的元素 public Buffer reset（）：将此缓冲区的位置重置为上一次标记的位置 public Buffer rewind（）：倒带缓冲区四大属性 非直接缓冲区（使用allocate方法获取缓冲区对象，创建在JVM内存中） allocate（）：获取非直接缓冲区对象 put（）：存入数据到缓冲区中 get（）：从缓冲区中读取数据 limit（）：获取缓冲区上限 position（）：获取缓冲区位置 capacity（）：获取缓冲区容量 mark（）：设置缓冲区标记 reset（）：重置position到上一次mark处 clear（）：清除缓冲区，position为0，limit为capacity //使用allocate分配一个指定大小的缓冲区 System.out.println(&quot;=================allocate()方法创建缓冲区后===================&quot;); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); System.out.println(&quot;position:&quot; + byteBuffer.position()); System.out.println(&quot;limit:&quot; + byteBuffer.limit()); System.out.println(&quot;capacity:&quot; + byteBuffer.capacity()); System.out.println(&quot;===================put()方法加入数据后=================&quot;); byteBuffer.put(&quot;HelloWorld!&quot;.getBytes()); System.out.println(&quot;position:&quot; + byteBuffer.position()); System.out.println(&quot;limit:&quot; + byteBuffer.limit()); System.out.println(&quot;capacity:&quot; + byteBuffer.capacity()); System.out.println(&quot;===================flip()方法切换读数据模式后================&quot;); byteBuffer.flip(); System.out.println(&quot;position:&quot; + byteBuffer.position()); System.out.println(&quot;limit:&quot; + byteBuffer.limit()); System.out.println(&quot;capacity:&quot; + byteBuffer.capacity()); System.out.println(&quot;===================get()方法读取数据后================&quot;); byte [] dst = new byte[byteBuffer.limit()]; byteBuffer.get(dst); System.out.println(&quot;position:&quot; + byteBuffer.position()); System.out.println(&quot;limit:&quot; + byteBuffer.limit()); System.out.println(&quot;capacity:&quot; + byteBuffer.capacity()); System.out.println(&quot;====================rewind()方法执行后===================&quot;); byteBuffer.rewind(); System.out.println(&quot;position:&quot; + byteBuffer.position()); System.out.println(&quot;limit:&quot; + byteBuffer.limit()); System.out.println(&quot;capacity:&quot; + byteBuffer.capacity()); System.out.println(&quot;==================clear()方法执行后=======================&quot;); byteBuffer.clear(); System.out.println(&quot;position:&quot; + byteBuffer.position()); System.out.println(&quot;limit:&quot; + byteBuffer.limit()); System.out.println(&quot;capacity:&quot; + byteBuffer.capacity()); /* clear()方法并非删除了数据，而是将position变为0，limit变为capacity的大小，数据依然存在 */ System.out.println((char)byteBuffer.get(1)); byte [] bys = new byte[1024]; System.out.println((char)byteBuffer.get(1)); byteBuffer.get(bys,0,2); System.out.println(new String(bys,0,bys.length)); byteBuffer.mark(); byteBuffer.get(bys,2,4); System.out.println(new String(bys,0,bys.length)); byteBuffer.reset(); byteBuffer.get(bys,2,4); System.out.println(new String(bys,0,bys.length)); =================allocate()方法创建缓冲区后=================== position:0 limit:1024 capacity:1024 ===================put()方法加入数据后================= position:11 limit:1024 capacity:1024 ===================flip()方法切换读数据模式后================ position:0 limit:11 capacity:1024 ===================get()方法读取数据后================ position:11 limit:11 capacity:1024 ====================rewind()方法执行后=================== position:0 limit:11 capacity:1024 ==================clear()方法执行后======================= position:0 limit:1024 capacity:1024 e He HelloW HelloW 直接缓冲区（使用allocateDirect方法获取缓冲区对象，创建在物理内存中） allocateDirect（）：获取直接缓冲区对象 put（）：存入数据到缓冲区中 get（）：从缓冲区中读取数据 limit（）：获取缓冲区上限 position（）：获取缓冲区位置 capacity（）：获取缓冲区容量 mark（）：设置缓冲区标记 reset（）：重置position到上一次mark处 clear（）：清除缓冲区，position为0，limit为capacity ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); byteBuffer.put(&quot;zhiyuan&quot;.getBytes()); System.out.println(byteBuffer.isDirect()); 直接缓冲区和非直接缓冲区的区别 7.4 通道（Channel） 用于建立起与IO设备的连接，结合缓冲区实现读写数据 特性 双向性：类似于流，但不同于InputStream和OutputStream，流具有单向性和独占性，通道则偏向于数据的流通性，一个Channel支持双向传输（输入、输出） 非阻塞式 操作唯一性 实现 文件类：FileChannel UDP类：DatagramChannel TCP类：ServerSocketChannel / SocketChannel 获取通道 通过支持通道的类提供的getChannel()获取 FileInputStream/FileOutputStream RandomAccessFile ServerSocket/Socket DatagramSocket JDK1.7以后NIO2针对各个类提供了静态方法open()获取 JDK1.7以后NIO2的Files工具类提供了newByteChannel()获取 利用通道完成文件的复制 //使用非直接缓冲区完成文件的复制 public static void test1() throws IOException { FileInputStream fis = new FileInputStream(&quot;E:\\\\壁纸\\\\壁纸\\\\212.jpg&quot;); FileOutputStream fos = new FileOutputStream(&quot;copy1.jpg&quot;); FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); while(inChannel.read(buffer)!=-1){ buffer.flip(); outChannel.write(buffer); buffer.clear(); } outChannel.close(); inChannel.close(); fos.close(); fis.close(); } //使用直接缓冲区通道写数据 public static void test2() throws IOException{ FileInputStream fis = new FileInputStream(&quot;E:\\\\壁纸\\\\壁纸\\\\212.jpg&quot;); FileOutputStream fos = new FileOutputStream(&quot;copy2.jpg&quot;); FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); while(inChannel.read(byteBuffer)!=-1){ byteBuffer.flip(); outChannel.write(byteBuffer); byteBuffer.clear(); } outChannel.close(); inChannel.close(); fos.close(); fis.close(); } //使用直接缓冲区完成文件的复制（使用内存映射文件的方式） public static void test3() throws IOException, URISyntaxException { FileChannel inChannel = FileChannel.open(Paths.get(&quot;E:\\\\壁纸\\\\壁纸\\\\212.jpg&quot;), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get(&quot;copy3.jpg&quot;),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE); //内存映射文件 MappedByteBuffer inMapBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size()); MappedByteBuffer outMapBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size()); //直接对缓冲区进行数据的读写操作 byte [] dst = new byte[inMapBuffer.limit()]; inMapBuffer.get(dst); outMapBuffer.put(dst); inChannel.close(); outChannel.close(); } //使用直接缓冲区完成文件复制（使用通道数据交换的方式） public static void test4() throws IOException{ FileChannel inChannel = FileChannel.open(Paths.get(&quot;E:\\\\壁纸\\\\壁纸\\\\212.jpg&quot;), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get(&quot;copy4.jpg&quot;),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE); inChannel.transferTo(0, inChannel.size(), outChannel); // outChannel.transferFrom(inChannel,0,inChannel.size()); inChannel.close(); outChannel.close(); } //耗时对比 public static void main(String[] args) throws IOException, URISyntaxException { long startTime = System.currentTimeMillis(); // test1();//52毫秒（非直接缓冲区使用Buffer复制） // test2();//41毫秒（直接缓冲区使用Buffer复制） // test3();//24毫秒（直接缓冲区使用文件映射复制） // test4();//17毫秒（直接缓冲区使用通道数据交换复制） long endTime = System.currentTimeMillis(); long useTime = endTime - startTime; System.out.println(&quot;耗时：&quot; + useTime + &quot;毫秒&quot;); } 分散（Scatter）读取和聚集（Gather）写入 分散读取：将通道中数据分散到多个缓冲区中 聚集写入：将多个缓冲区的数据聚集到通道中 //首先创建一个随机访问文件 RandomAccessFile raf= new RandomAccessFile(&quot;test.txt&quot;, &quot;rw&quot;); //根据随机获取通道 FileChannel channel = raf.getChannel(); //分配指定大小指定缓冲区 ByteBuffer buf1=ByteBuffer.allocateDirect(200); ByteBuffer buf2=ByteBuffer.allocateDirect(1024); // 分散读取 ByteBuffer[] bufs={buf1,buf2}; channel.read(bufs);//采用通道分散读取数据 for (ByteBuffer byteBuffer : bufs) { // 切换成读模式 byteBuffer.flip(); } System.out.println(new String(bufs[0].array(),0,bufs[0].limit())); System.out.println(&quot;-------------------------------------------------&quot;); System.out.println(new String(bufs[1].array(),1,bufs[1].limit())); System.out.println(&quot;------聚集写入---------&quot;); //创建一个随机写入文件 RandomAccessFile raf2= new RandomAccessFile(&quot;test2.txt&quot;, &quot;rw&quot;); //获取写入通道 FileChannel channel2 = raf2.getChannel(); channel2.write(bufs);//采用通道写入数据 raf2.close();//关闭 raf.close();//关闭 7.5 字符集（Charset） 编码：字符串 -&gt; 字节数组 解码：字节数组 -&gt; 字符串 public static void main(String[] args) { SortedMap&lt;String, Charset&gt; map = Charset.availableCharsets(); Set&lt;Map.Entry&lt;String, Charset&gt;&gt; set = map.entrySet(); for (Map.Entry&lt;String, Charset&gt; s : set) { System.out.println(s.getKey() + &quot;\\t&quot; + s.getValue() + &quot;\\t&quot; + s.getClass()); } //创建编码格式 Charset gbk = Charset.forName(&quot;GBK&quot;); //获取编码器 CharsetEncoder charsetEncoder = gbk.newEncoder(); //获取解码器 CharsetDecoder charsetDecoder = gbk.newDecoder(); //编码 CharBuffer cb = CharBuffer.allocate(1024); cb.put(&quot;HelloWord!&quot;); cb.flip(); ByteBuffer encode = charsetEncoder.encode(cb); System.out.print(&quot;[&quot;); for (int i = 0; i &lt; encode.limit(); i++) { System.out.print(encode.get(i)+&quot;,&quot;); } System.out.print(&quot;]&quot;); //解码 encode.rewind(); CharBuffer decode = charsetDecoder.decode(encode); System.out.print(&quot;\\n[&quot;); for (int i = 0; i &lt; decode.limit(); i++) { System.out.print(decode.get(i)+&quot;,&quot;); } System.out.print(&quot;]&quot;); } 7.6 NIO的非阻塞简述 在客户端与服务器之间，建立一个选择器（Selector），将每一个用于传输数据的通道（Channel）注册到该选择器（Selector）上，选择器（Selector）来监控各个通道的IO（WRITE、READ、ACCEPT、CONNECT）状态，当客户端IO状态准备就绪时，选择器（Selector）通知服务器开始处理各种IO 7.7 NIO阻塞式编程 三个核心 第一步：创建通道（Channel），负责建立IO设备连接 java.nio.channels.Channel接口 SelectableChannel ServerSocketChannel SocketChannel DatagramChannel Pipe.SinkChannel Pipe.SourceChannel FileChannel不能切换成非阻塞模式 第二步：创建缓冲区（Buffer），负责数据的存取 第三步：创建选择器（Selector），是SelectableChannel的多路复用器，负责监控SelectableChannel的IO状态 public class TCPDemo { @Test public void client() throws IOException { SocketChannel socketChannel = null; FileChannel inChannel = null; //1.建立IO通道 try { socketChannel = SocketChannel.open(new InetSocketAddress(&quot;127.0.0.1&quot;,8090)); } catch (IOException e) { e.printStackTrace(); } //2.建立缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); //3.读取本地图片发送到服务器 try { inChannel = FileChannel.open(Paths.get(&quot;E:\\\\壁纸\\\\壁纸\\\\212.jpg&quot;), StandardOpenOption.READ); } catch (IOException e) { e.printStackTrace(); } while(inChannel.read(byteBuffer)!=-1){ byteBuffer.flip(); socketChannel.write(byteBuffer); byteBuffer.clear(); } inChannel.close(); socketChannel.close(); } @Test public void server() throws IOException { //1.建立IO通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2.绑定端口号 serverSocketChannel.bind(new InetSocketAddress(8090)); //3.获取客户端连接的通道 SocketChannel socketChannel = serverSocketChannel.accept(); //4.接收客户端的数据，并保存到本地 FileChannel outChannel = FileChannel.open(Paths.get(&quot;client1.jpg&quot;), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); while(socketChannel.read(byteBuffer)!=-1){ byteBuffer.flip(); outChannel.write(byteBuffer); byteBuffer.clear(); } //5.关闭资源 socketChannel.close(); serverSocketChannel.close(); outChannel.close(); } } 7.8 NIO非阻塞式编程 TCP UDP 7.9 管道（Pipe） //1. 获取Pipe管道 Pipe pipe = Pipe.open(); //2. 将缓冲区中的数据写入管道 ByteBuffer buffer = ByteBuffer.allocate(1024); Pipe.SinkChannel sinkChannel = pipe.sink(); buffer.put(&quot;Pipe&quot;.getBytes()); buffer.flip(); sinkChannel.write(buffer); //3. 读取缓冲区中的数据 Pipe.SourceChannel sourceChannel = pipe.source(); buffer.flip(); int length = sourceChannel.read(buffer); System.out.println(new String(buffer.array(),0,length)); sourceChannel.close(); sinkChannel.close(); 7.2 Selector：选择器/多路复用器 作用：I/O就绪状态选择 地位：NIO网络编程的基础 事件 SelectionKey.OP_CONNECT（连接就绪） SelectionKey.OP_ACCEPT（接受就绪） SelectionKey.OP_READ（读就绪） SelectionKey.OP_WRITE（写就绪） 实现 //创建Selector Selector selector = Selector.open(); //将Channel注册到Selector上，监听读就绪事件 SelectionKey selectionKey = channel.register(selector,SelectionKey.OP_READ); //阻塞等待Channel有就绪事件发生 int selectNum = selector.select(); //获取发生就绪事件的Channel集合 Set&lt;SelectionKey&gt; selectedKeys = selector.selectedKeys(); 7.4 NIO编程实现步骤 第一步：创建Selector 第二步：创建ServerSocketChannel，并绑定监听端口 第三步：将Channel设置为非阻塞模式 第四步：将Channel注册到Selector上，监听连接事件 第五步：循环调用Selector的select方法，检查就绪情况 第六步：调用selectedKeys方法获取就绪Channel集合 第七步：判断就绪事件种类，调用业务处理方法 第八步：根据业务需要，决定是否再次注册监听事件，重复执行第三步操作 7.5 NIO网络编程实战 7.6 分散读取Scatter和聚集写入Gather 分散读取：从Channel中读取数据分散到多个Buffer中 聚集写入：把多个Buffer的数据聚集写入Channel中 public static void main(String[] args){ RandomAccessFile raf = new RandomAccessFile(&quot;a.txt&quot;,&quot;rw&quot;); FileChannel channel = raf.getChannel(); ByteBuffer bb1 = ByteBuffer.allocate(10*3); ByteBuffer bb2 = ByteBuffer.allocate(100*3); Byter[] bbs = {bb1,bb2}; channel.read(bbs); for(ByteBuffer bb : bbs){ bb.flip(); System.out.println(new String(bb.array(),0,bb.limit)); } System.out.println(&quot;-----------------------------------&quot;); RandomAccessFile raf2 = new RandomAccessFile(&quot;b.txt&quot;,&quot;rw&quot;); FileChannel channel2 = raf2.getChannel(); channel2.write(bb2); } 7.7 编解码 Charset 80. 反射和注解 1. 反射 1.1 类加载 概述：当程序要使用某个类时，如果该类还未被加载进内存中，则系统会通过**类的加载，类的连接，类的初始化**这三个步骤来对类进行初始化 类加载 将class文件读入内存，并为其创建一个java.lang.Class对象 任何类被使用时，系统都会为其建立一个java.lang.Class对象 类的连接 验证阶段：用于检验被加载的类是否有正确的内部结构，并和其他类协调一致 准备阶段：负责为类的类变量分配内存，并设置默认初始值 解析阶段：将类的二进制数据中的符号引用替换为直接引用 类的初始化 在该阶段，主要就是对类变量进行初始化 类的初始化步骤 假如类还未被加载和连接，则程序先加载并连接该类 假如该类的直接父类还未被初始化，则先初始化其直接父类（初始化直接父类也遵循1-3） 假如类中有初始化语句，则系统依次执行这些初始化语句 类的初始化时机 创建类的实例 调用类的类方法 访问类或者接口的类变量，或者为类变量赋值 使用反射方式来强制创建某个类或者接口对应的java.langClass对象 初始化某个类的子类 直接使用java.exe命令来运行某个主类 1.2 类加载器 作用：负责将class文件加载进内存中，并为之生成对应的java.lang.Class对象 JVM的类加载机制 全盘负责：就是当一个类加载器负责加载某个class时，该class所依赖的和引用的其他class也将由类加载器负责载入，除非显式使用另外一个类加载器来载入 父类委托：就是当一个类加载器负责加载某个class时，先让父类加载器试图加载该class，只有在父类加载器无法加载该类时才尝试从自己的了一路径中加载该类 缓存机制：保证所有加载过的class都会被缓存，当程序需要使用某个class对象时，类加载器先从缓存区中搜索该class，只有当缓存区中不存在该class对象时，系统才会读取该类对应的二进制数据，并将其转换成class对象，存储到缓存区 包：java.lang.ClassLoader（抽象类） 常用方法： public ClassLoader getParent（）：返回父类加载器 public static ClassLoader getSystemClassLoader（）：返回系统类加载器 Java运行时内置类加载器 BootstrapClassLoader：虚拟机内置类加载器，通常为null ExtensionClassLoader：平台类加载器（JDK1.9变为PlatFormClassLoader） The extension class loader has been renamed; it is now the platform class loader. All classes in the Java SE Platform are guaranteed to be visible through the platform class loader. In addition, the classes in modules that are standardized under the Java Community Process but not part of the Java SE Platform are guaranteed to be visible through the platform class loader. Just because a class is visible through the platform class loader does not mean the class is actually defined by the platform class loader. Some classes in the Java SE Platform are defined by the platform class loader while others are defined by the bootstrap class loader. Applications should not depend on which class loader defines which platform class. SystemClassLoader：应用程序类加载器 类加载器的继承关系：System的父加载器为Extension，而Extension的父加载器为Bootstrap public class Demo { public static void main(String[] args) { ClassLoader c1 = ClassLoader.getSystemClassLoader(); System.out.println(c1);//AppClassLoader ClassLoader c2 = c1.getParent(); System.out.println(c2);//ExtClassLoader ClassLoader c3 = c2.getParent(); System.out.println(c3);//null } } //sun.misc.Launcher$AppClassLoader@18b4aac2 //sun.misc.Launcher$ExtClassLoader@1b6d3586 //null 1.3 反射 概述：是指在运行时获取一个类的变量和方法信息。然后通过获取到的信息来创建对象，调用方法的一种机制，由于这种动态性，可以极大地增强程序的灵活性，程序不用在编译期就完成确定，在运行期仍然可以扩展 获取Class类的对象 使用class属性，获取所属类对应的对象 使用getClass（）方法，获取所属类对应的对象（Object类中的方法） 使用Class类中的静态方法forName（String className），字符串参数为某个类的全路径 public class Demo2 { public static void main(String[] args) { Class&lt;Student&gt; c1 = Student.class; Student stu = new Student(); Class&lt;? extends Student&gt; c2 = stu.getClass(); Class&lt;?&gt; c3 = null; try { c3 = Class.forName(&quot;com.demo.javase.Student&quot;); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(&quot;c1:&quot;+c1+&quot;\\tc2:&quot;+c2+&quot;\\tc3:&quot;+c3); } } //c1:class com.demo.javase.Student c2:class com.demo.javase.Student c3:class com.demo.javase.Student 反射获取构造方法并使用 public Constructor&lt;?&gt;[ ] getConstructors（）：获取所有public的构造方法 public Constructor&lt;?&gt;[ ] getDeclaredConstructors（）：获取所有构造方法 public Constructor getConstructor（Class&lt;?&gt; ... parameterTypes）：获取指定public构造方法 public Constructor getDeclaredConstructor（Class&lt;?&gt; ... parameterTypes）：获取指定构造方法 public T newInstance（Object... initargs）：根据指定构造方法创建对象 //获取class对象 Class&lt;?&gt; c = Class.forName(&quot;com.demo.javase.Student&quot;); Constructor&lt;?&gt;[] cons = c.getConstructors(); for(Constructor con : cons){ System.out.println(con); } //public com.demo.javase.Student(java.lang.String,int) //public com.demo.javase.Student() Constructor&lt;?&gt;[] allCons = c.getDeclaredConstructors(); for (Constructor&lt;?&gt; allCon : allCons) { System.out.println(allCon); } //public com.demo.javase.Student(java.lang.String,int) //com.demo.javase.Student(int) //private com.demo.javase.Student(java.lang.String) //public com.demo.javase.Student() //基本类型和引用类型都可以通过class属性来获取对应的类对象 Constructor&lt;?&gt; con = c.getConstructor(String.class,int.class); Object o = con.newInstance(&quot;zhiyuan&quot;,23); System.out.println(o); //学生{name='zhiyuan', age=23} Constructor&lt;?&gt; con2 = c.getDeclaredConstructor(int.class); System.out.println(con2.newInstance(22)); //学生{name='null', age=22} //私有构造方法可以获取到但无法使用私有构造方法创建对象 Constructor&lt;?&gt; con3 = c.getDeclaredConstructor(String.class); //System.out.println(con3.newInstance(&quot;zhiyuan&quot;)); 暴力反射（使用私有构造方法创建对象） //私有构造方法可以获取到但无法使用私有构造方法创建对象，若要使用，可以用暴力反射 Constructor&lt;?&gt; con3 = c.getDeclaredConstructor(String.class); con3.setAccessible(true); System.out.println(con3.newInstance(&quot;zhiyuan&quot;)); //学生{name='zhiyuan', age=0} 反射获取成员变量并使用 public Field[ ] getFields（）：获取所有公共成员变量和类变量 public Field getField（String name）：获取指定公共成员变量和类变量 public Field[ ] getDeclaredFields（）：获取所有成员变量和类变量 public Field getDeclaredField（String name）：获取指定成员变量和类变量 Field类中用于给变量赋值的方法 public void set（Object obj，Object value）：给obj对象的成员变量赋值为value Class&lt;?&gt; c = Class.forName(&quot;com.demo.javase.Student&quot;); Field[] fields = c.getFields(); for (Field field : fields) { System.out.println(field); } //public java.lang.String com.demo.javase.Student.gender Field[] declaredFields = c.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); } //private java.lang.String com.demo.javase.Student.name //private int com.demo.javase.Student.age //int com.demo.javase.Student.count //public java.lang.String com.demo.javase.Student.gender //static int com.demo.javase.Student.num Field genderField = c.getField(&quot;gender&quot;); Constructor&lt;?&gt; con = c.getConstructor(String.class,int.class); Object o = con.newInstance(&quot;zhiyuan&quot;,25); genderField.set(o,&quot;male&quot;); System.out.println(o); //Student{name='zhiyuan', age=25, count=0, gender='male'} 暴力反射（给私有成员变量赋值） Field nameField = c.getDeclaredField(&quot;name&quot;); Object zhiyuan = con.newInstance(&quot;zhiyuan&quot;, 26); nameField.setAccessible(true); nameField.set(zhiyuan,&quot;zhiyuan002&quot;); System.out.println(zhiyuan); //Student{name='zhiyuan002', age=26, count=0, gender='null'} 反射获取成员方法并使用 public Method [ ] getMethods（）：获取所有公共的方法（包含继承过来的方法） public Method [ ] getDeclaredMethods（）：获取所有的方法（不包含继承过来的方法） public Method getMethod（）：获取指定的公共方法 public Method getDeclaredMethod（）：获取指定的方法 Method类中用于使用方法的方法 public Object invoke（Object obj，Object... args）：obj为调用方法的对象，args是方法需要的参数 Class&lt;?&gt; c = Class.forName(&quot;com.demo.javase.Student&quot;); Method[] methods = c.getMethods(); for (Method method : methods) { System.out.println(method); } System.out.println(&quot;=================================================================&quot;); Method[] declaredMethods = c.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod); } /* public java.lang.String com.demo.javase.Student.toString() public java.lang.String com.demo.javase.Student.getName() public void com.demo.javase.Student.setName(java.lang.String) public int com.demo.javase.Student.getAge() public void com.demo.javase.Student.setAge(int) public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() ================================================================= public java.lang.String com.demo.javase.Student.toString() public java.lang.String com.demo.javase.Student.getName() public void com.demo.javase.Student.setName(java.lang.String) public int com.demo.javase.Student.getAge() private void com.demo.javase.Student.show() static void com.demo.javase.Student.why() public void com.demo.javase.Student.setAge(int) */ Method setName = c.getMethod(&quot;getName&quot;); Constructor&lt;?&gt; con = c.getConstructor(String.class, int.class); Object zhiyuan = con.newInstance(&quot;zhiyuan&quot;, 22); Object o = setName.invoke(zhiyuan); System.out.println(o); //zhiyuan Method setName = c.getMethod(&quot;setName&quot;,String.class); Constructor&lt;?&gt; con = c.getConstructor(String.class, int.class); Object zhiyuan = con.newInstance(&quot;zhiyuan&quot;, 22); Object o = setName.invoke(zhiyuan,&quot;Hulk&quot;); System.out.println(o); System.out.println(zhiyuan); //null //Student{name='Hulk', age=22, count=0, gender='null'} 暴力反射（调用私有方法） Method show = c.getDeclaredMethod(&quot;show&quot;); Constructor&lt;?&gt; con2 = c.getConstructor(String.class, int.class); Object hulk = con2.newInstance(&quot;Hulk&quot;, 50); show.setAccessible(true); show.invoke(hulk); 1.4 反射练习 向**ArrayList&lt;Integer&gt;**集合中添加一个字符串数据 ArrayList&lt;Integer&gt; integers = new ArrayList&lt;&gt;(); Class&lt;?&gt; c = integers.getClass(); Method add = c.getDeclaredMethod(&quot;add&quot;, Object.class); add.invoke(integers,&quot;我吐&quot;); add.invoke(integers,&quot;反射真神奇&quot;); System.out.println(integers); //[我吐, 反射真神奇] 2. 注解 2.1 概述： 注解（Annotation），一种代码级别的说明，它是JDK1.5以后版本引入的一个特性，与类、接口、枚举在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等前面，用来对这些元素进行说明，注释。 作用： 编写文档：通过代码里的标识的注解生成文档 代码分析：通过代码里的标识的注解对代码进行分析（反射） 编译检查：通过代码里的标识的注解让编译器能够实现基本的编译检查（@Override） 2.2 JDK预定义注解 @Override：检测被该注解标注的方法是否继承自父类 @Deprecate：该注解标注的内容，表示已过时 @SuppressWarnings：压制警告 @SuppressWarnings(&quot;all&quot;)：压制所有警告 2.3 自定义注解 格式： public @interface 注解名称{} 本质：注解本质上就是一个接口，继承自java.lang.annotation.Annotation public interface MyAnno extends java.lang.annotation.Annotation{} 属性：接口中可以定义的抽象方法 要求： 属性的返回值类型 基本数据类型 String 枚举 注解 以上类型的数组 定义了属性，需要给属性赋值 如果定义属性时，使用default关键字给属性默认初始化值，则使用注解时可以不进行属性的赋值 如果只有一个属性需要赋值，并且属性名是value，则value可以忽略不写，直接定义值即可 数组赋值时，使用大括号{}包裹，如果数组中只有一个值，则大括号可以省略 元注解：用于描述注解的注解 @Target：描述注解能够作用的范围 ElementType取值 TYPE：可以作用于类上，接口上，注解接口上，枚举类上 METHOD：可以作用于方法上 FIELD：可以作用于成员变量上，枚举常量上 @Retention：描述注解被保留的阶段 RetentionPolicy取值 SOURCE：注解保留到源码中，编译为字节码class文件中就不存在了 CLASS：注解保留在字节码class文件中，不必被JVM读取 RUNTIME：注解保留到字节码class文件中，并被JVM读取（可以通过反射方式读取） @Documented：描述注解是否被抽取到api文档中 @Inherited：描述注解是否被子类继承 在程序中使用注解：获取注解中定义的属性值 获取注解定义的位置的对象 获取指定的注解 getAnnotation（Class） 调用注解中的抽象方法获取配置的属性值 小结： 以后大多数时候，我们会使用注解，而不是自定义注解 注解给谁用： 编译器 解析程序 注解不是程序的一部分 2.4 代码实现 //简单的实现：解析注解属性值，调用方法 //材料：一个自定义注解，一个测试类，一个解析注解类 //1.自定义注解 @Target(value={ElementType.TYPE,ElementType.FIELD,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { public abstract String className(); public abstract String methodName(); } //2.测试类 public class AnnotationDemo { public void show(){ System.out.println(&quot;show()方法&quot;); } } //3.注解解析类 @MyAnnotation(className = &quot;com.demo.javase.AnnotationDemo&quot;,methodName = &quot;show&quot;) public class Demo1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { //获取当前解析类的对象 Class&lt;Demo1&gt; c = Demo1.class; //获取注解对象（本质上就是在内存中实现了一个注解的类的对象） MyAnnotation annotation = c.getAnnotation(MyAnnotation.class); /* * public MyAnnotionImp implements MyAnnotation{ * public String className(){ * return &quot;com.demo.javase.AnnotationDemo&quot;; * } * public String methodName(){ * return &quot;show&quot;; * } * } * */ //获取注解对象的属性值 String className = annotation.className(); String methodName = annotation.methodName(); System.out.println(className); System.out.println(methodName); //获取测试类的对象 Class&lt;?&gt; c2 = Class.forName(className); //创建测试类的实例化对象 Object o = c2.newInstance(); //获取对应名称的方法 Method method = c2.getMethod(methodName); //执行方法 method.invoke(o); } } //利用注解实现程序检错 //材料：一个自定义注解，一个测试类，一个解析注解检错类 //1.自定义注解 @Target(value={ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) @Retention(value = RetentionPolicy.RUNTIME) @Documented public @interface Check { } //2.测试类 public class Demo2 { @Check public void add(){ System.out.println(&quot;1+0=&quot;+(1+0)); } @Check public void subtract(){ System.out.println(&quot;1-0=&quot;+(1-0)); } @Check public void multiply(){ System.out.println(&quot;1*0=&quot;+(1*0)); } @Check public void divide(){ System.out.println(&quot;1/0=&quot;+(1/0)); } } //3.解析注解检错类 public class CheckDemo { public static void main(String[] args) throws IOException { Demo2 demo2 = new Demo2(); Class&lt;? extends Demo2&gt; aClass = demo2.getClass(); Method[] methods = aClass.getMethods(); int number = 0; BufferedWriter bw = new BufferedWriter(new FileWriter(&quot;bug.txt&quot;,true)); for (Method method : methods) { if(method.isAnnotationPresent(Check.class)){ try { method.invoke(demo2); } catch (Exception e){ number++; bw.write(&quot;问题:&quot;+method.getName()+&quot;()方法出现异常&quot;); bw.newLine(); bw.write(&quot;异常:&quot;+e.toString()); bw.newLine(); } } } if(number &gt; 0){ bw.write(&quot;类名：&quot;+ demo2.getClass().getName()); bw.newLine(); bw.write(&quot;本次总共检测到&quot;+number+&quot;个错误&quot;); bw.newLine(); bw.write(&quot;检测时间：&quot;+new SimpleDateFormat(&quot;yyyy年MM月dd日 kk时mm分ss秒&quot;).format(new Date(System.currentTimeMillis()))); bw.newLine(); }else{ bw.write(&quot;恭喜你，本次检测此程序没有错误&quot;); } bw.write(&quot;---------------------------------------------&quot;); bw.newLine(); bw.flush(); bw.close(); System.out.println(number); } } //bug.txt的结果 /* 问题:divide()方法出现异常 异常:java.lang.reflect.InvocationTargetException 类名：com.demo.javase.demo.Demo2 本次总共检测到1个错误 检测时间：2020年12月15日 12时39分35秒 */ 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/3pOEFSCc4/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/3pOEFSCc4/"},{"title":"常用API收集（第一期：2021.2.19）","content":"一、B站API 1. 热门搜索API https://s.search.bilibili.com/main/hotword?mid=&amp;buvid=034F9BB5-4350-41BB-90DC-24287F103A7D138368infoc&amp;jsonp=jsonp&amp;callback=jsonCallback_bili_66770627113836521 2. 获取视频信息API https://api.bilibili.com/x/web-interface/archive/stat?bvid={视频BV号} https://api.bilibili.com/x/player/pagelist?bvid={视频BV号} https://api.bilibili.com/x/web-interface/view?aid={视频AV号} https://api.bilibili.com/x/web-interface/view?bvid={视频BV号} https://api.bilibili.com/x/web-interface/view?cid={视频cid} 上述多个接口既可以获取视频详细信息，也可以获取视频简单信息，具体哪个是哪个，我忘记了，请读者带入参数自行测试 3. 获取弹幕API https://comment.bilibili.com/{cid}.xml 将视频的cid与此url拼接，发送get请求，即可获取该视频弹幕信息 4. 获取视频cidAPI https://api.bilibili.com/x/player/pagelist?bvid={BV号} 5. B站每日排行榜API https://api.bilibili.com/x/web-interface/ranking/v2?rid=0&amp;type=all 6. B站入站必刷榜单API https://api.bilibili.com/x/web-interface/popular/precious?page_size=100&amp;page=1 7. B站每周必看API https://api.bilibili.com/x/web-interface/popular/series/one?number={期数} 期数从1开始，具体哪一期，请参考B站上的期数获取指定信息，截止到2021.2.19已更新到99期 8. B站小黑屋API https://api.bilibili.com/x/credit/blocked/list?jsonp=jsonp&amp;otype=0&amp;pn={页数}&amp;ps=20&amp;callback=__jp0 因为B站一次性展示内容只有10条左右，会根据滚动条动态发送ajax请求加载数据，所以需要每次提供加载的页数，就可加载到对应资源 其他参数没做研究，但猜测大致是来源和类型，请读者自行获取相关信息 二、 知乎API 1. 知乎每日热榜API https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50&amp;desktop=true 原文作者：絷缘 作者邮箱：zhiyuandnc666@88.com 原文地址：https://zhiyuandnc.github.io/IW8mQ0Bvy/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/IW8mQ0Bvy/"},{"title":"数组模拟实现循环队列","content":"一、代码实现 public class CircularArrayQueue { /** * 队列最大容量 */ private int maxSize; /** * 队列头指针（指向首元素) */ private int front; /** * 队列尾指针（指向尾元素的后一个位置） */ private int rear; /** * 数组模拟队列容器 */ private int[] arr; /** * 初始化循环队列 * @param maxSize int */ public CircularArrayQueue(int maxSize){ this.maxSize = maxSize; arr = new int[maxSize]; rear = front = 0; } /** * 判断队列是否已满 * @return isFull boolean */ public boolean isFull(){ return (rear+1)%maxSize==front; } /** * 判断队列是否为空 * @return isEmpty boolean */ public boolean isEmpty(){ return rear==front; } /** * 入队列 * @param n 元素值 */ public void addQueue(int n){ //队满不能入队 if(isFull()){ throw new RuntimeException(&quot;队列已满，无法加入数据！&quot;); } arr[rear] = n; rear = (rear+1)%maxSize; } /** * 出队列 * @return int */ public int getQueue(){ //队空不能出队 if(isEmpty()){ throw new RuntimeException(&quot;队列已空，无法取得数据！&quot;); } int value = arr[front]; front = (front+1)%maxSize; return value; } /** * 遍历队列所有元素 */ public void showQueue(){ if(isEmpty()){ System.out.println(&quot;队列为空！&quot;); } for (int i = front; i &lt; front + size(); i++) { System.out.println(arr[i%maxSize] + &quot;\\t&quot;); } } /** * 获取当前队列元素个数 * @return size int */ public int size(){ return (rear-front+maxSize)%maxSize; } /** * 获取队首元素 * @return */ public int headQueue(){ if(isEmpty()){ throw new RuntimeException(&quot;队列已空，无法取得数据！&quot;); } return arr[front]; } } 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/wN4eqwrOE/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/wN4eqwrOE/"},{"title":"数组模拟实现队列","content":"一、队列 特点：先入先出 分析：因为队列队首队尾都可以操作，所以需要两个变量模拟指针 front 和 rear 分别指向首元素的前一个位置和尾元素，使用数组实现队列 入队列：rear后移，元素入队 出队列：front后移，元素出队 二、代码实现 public class ArrayQueue{ /** * 队列最大容量 */ private int maxSize; /** * 队列头指针（指向首元素的前一个位置） */ private int front; /** * 队列尾指针（指向尾元素） */ private int rear; /** * 数组模拟队列容器 */ private int[] arr; /** * 初始化队列 * @param maxSize int */ public ArrayQueue(int maxSize){ this.maxSize = maxSize; arr = new int[maxSize]; //初始化状态下头指针与尾指针相同，均为-1 front = rear = -1; } /** * 判断队列是否已满 * @return boolean */ public boolean isFull(){ return rear == maxSize - 1; } /** * 判断队列是否为空 * @return boolean */ public boolean isEmpty(){ return rear == front; } /** * 入队列 * @param data int */ public void addQueue(int data){ //队满不能入队 if(isFull()){ throw new RuntimeException(&quot;队列已满，无法加入数据！&quot;); } //rear后移，元素入队列 arr[++rear] = data; } /** * 出队列 * @return int */ public int getQueue(){ //队空不能出队 if(isEmpty()){ throw new RuntimeException(&quot;队列已空，无法取得数据！&quot;); } //front后移，元素出队列 return arr[++front]; } /** * 遍历队列所有元素 */ public void showQueue(){ //队空无需遍历 if(isEmpty()){ System.out.println(&quot;队列为空！&quot;); return ; } for (int i = 0; i &lt; arr.length; i++) { System.out.print(arr[i] + &quot;\\t&quot;); } } /** * 获取队首元素 * @return int */ public int headQueue(){ //队空不返回 if(isEmpty()){ throw new RuntimeException(&quot;队列为空！&quot;); } return arr[front+1]; } } 三、测试队列功能 public class ArrayQueueDemo { public static void main(String[] args) { ArrayQueue arrayQueue = new ArrayQueue(10); for (int i = 0; i &lt; 10; i++) { arrayQueue.addQueue(10); } arrayQueue.showQueue(); // arrayQueue.addQueue(10); for (int i = 0; i &lt; 10; i++) { arrayQueue.getQueue(); } // arrayQueue.getQueue(); System.out.println(arrayQueue.isEmpty()); System.out.println(arrayQueue.isFull()); System.out.println(arrayQueue.headQueue()); } } 四、控制台结果 10 10 10 10 10 10 10 10 10 10 true true Exception in thread &quot;main&quot; java.lang.RuntimeException: 队列为空！ at com.zhiyuan.datastructure.queue.array.ArrayQueue.headQueue(ArrayQueue.java:100) at com.zhiyuan.datastructure.queue.array.ArrayQueueDemo.main(ArrayQueueDemo.java:22) 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/IjOXWGTmT/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/IjOXWGTmT/"},{"title":"稀疏数组实现棋盘存储与恢复","content":"一、导入Jackson依赖 为了将对象序列化为XML文件，将XML反序列化为对象 &lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.dataformat&lt;/groupId&gt; &lt;artifactId&gt;jackson-dataformat-xml&lt;/artifactId&gt; &lt;version&gt;2.9.8&lt;/version&gt; &lt;/dependency&gt; 二、创建棋盘数据信息Bean对象 将稀疏数组存储为对象 public class SparseArray { private int row; private int col; private int count; private Array [] data; public SparseArray() {} public SparseArray(int row, int col, int count, Array[] data) { this.row = row; this.col = col; this.count = count; this.data = data; } public void setRow(int row) { this.row = row; } public void setCol(int col) { this.col = col; } public void setCount(int count) { this.count = count; } public void setData(Array[] data) { this.data = data; } public int getRow() { return row; } public int getCol() { return col; } public int getCount() { return count; } public Array[] getData() { return data; } } public class Array { private int row; private int col; private int value; public Array() {} public Array(int row, int col, int value) { this.row = row; this.col = col; this.value = value; } public void setRow(int row) { this.row = row; } public void setCol(int col) { this.col = col; } public void setValue(int value) { this.value = value; } public int getRow() { return row; } public int getCol() { return col; } public int getValue() { return value; } } 三、实现棋盘存储与恢复 将数据对象序列化到XML配置文件中，实现存储棋盘 将XML配置反序列化为数据对象，实现恢复棋盘 public class SparsearrayDemo1 { public static void main(String[] args) { // 创建一个原始的二维数组 // 0：表示没有棋子，1：表示黑子，2：表示白子 int [][]chessArray = new int [11][11]; chessArray[1][2] = 1; chessArray[2][3] = 2; chessArray[4][5] = 2; System.out.println(&quot;原数组内容：&quot;); for (int[] ints : chessArray) { System.out.println(Arrays.toString(ints)); } // 遍历二维数组获得非零元素个数 int count = 0; for (int[] value : chessArray) { for (int j = 0; j &lt; value.length; j++) { if (value[j] != 0) { count++; } } } System.out.println(&quot;非零个数：&quot; + count); int [][] sparseArray = new int [count+1][3]; int row = chessArray.length; int col = chessArray[0].length; sparseArray[0][0] = row; sparseArray[0][1] = col; sparseArray[0][2] = count; int num = 0; for (int i = 0; i &lt; chessArray.length; i++) { for (int j = 0; j &lt; chessArray[i].length; j++) { if (chessArray[i][j]!=0){ sparseArray[++num][0] = i; sparseArray[num][1] = j; sparseArray[num][2] = chessArray[i][j]; } } } //创建存储棋盘数据的对象 SparseArray info = new SparseArray(); info.setRow(sparseArray[0][0]); info.setCol(sparseArray[0][1]); info.setCount(sparseArray.length-1); Array [] arrays = new Array[sparseArray.length-1]; for (int i = 1; i &lt; sparseArray.length; i++) { Array array = new Array(); array.setRow(sparseArray[i][0]); array.setCol(sparseArray[i][1]); array.setValue(sparseArray[i][2]); arrays[i-1] = array; } info.setData(arrays); System.out.println(&quot;开始存储棋盘1&quot;); File file = new File(&quot;./sparsearray.xml&quot;); saveBoard(file,info); System.out.println(&quot;棋盘存储成功&quot;); System.out.println(&quot;开始恢复棋盘&quot;); int[][] ints = recoverBoard(file, SparseArray.class); for (int[] anInt : ints) { System.out.println(Arrays.toString(anInt)); } System.out.println(&quot;棋盘恢复成功&quot;); } public static void saveBoard(File file,SparseArray info){ //将对象序列化为xml文件存储 try { if(!file.exists()){ file.createNewFile(); } fromObjectToXML(file,info); } catch (IOException e) { e.printStackTrace(); } } public static int [][] recoverBoard(File file,Class cls){ if(file.exists()){ SparseArray info = (SparseArray)fromXMLToObject(file, cls); int [][] newArray = new int [info.getRow()][info.getCol()]; Array[] data = info.getData(); for (int i = 0; i &lt; data.length; i++) { newArray[data[i].getRow()][data[i].getCol()] = data[i].getValue(); } return newArray; } return null; } public static void fromObjectToXML(File file,Object data){ BufferedWriter bw = null; XmlMapper xmlMapper = new XmlMapper(); try { String xml = xmlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(data); bw = new BufferedWriter(new FileWriter(file)); bw.write(xml,0,xml.length()); } catch (JsonProcessingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } public static Object fromXMLToObject(File file,Class cls){ BufferedReader br = null; XmlMapper xmlMapper = new XmlMapper(); Object sparseArray = null; try { sparseArray = xmlMapper.readValue(new FileInputStream(file), cls); } catch (IOException e) { e.printStackTrace(); } return sparseArray; } } 四、查看存储的棋局 &lt;SparseArray&gt; &lt;row&gt;11&lt;/row&gt; &lt;col&gt;11&lt;/col&gt; &lt;count&gt;3&lt;/count&gt; &lt;data&gt; &lt;data&gt; &lt;row&gt;1&lt;/row&gt; &lt;col&gt;2&lt;/col&gt; &lt;value&gt;1&lt;/value&gt; &lt;/data&gt; &lt;data&gt; &lt;row&gt;2&lt;/row&gt; &lt;col&gt;3&lt;/col&gt; &lt;value&gt;2&lt;/value&gt; &lt;/data&gt; &lt;data&gt; &lt;row&gt;4&lt;/row&gt; &lt;col&gt;5&lt;/col&gt; &lt;value&gt;2&lt;/value&gt; &lt;/data&gt; &lt;/data&gt; &lt;/SparseArray&gt; 五、控制台打印结果 原数组内容： [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 非零个数：3 开始存储棋盘 棋盘存储成功 开始恢复棋盘 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 棋盘恢复成功 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/ZZGM9B12P/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/ZZGM9B12P/"},{"title":"Redis数据库简单学习","content":" redis是一款高性能的NOSQL系列的非关系型数据库（key-value） 官网：https://redis.io/ 中文网：https://www.redis.net.cn/ 一、非关系型数据库 1. 特点 数据之间没有关联关系 数据存储在内存中 2. 解决的问题 经常查询的一些不会经常发生变化的数据 缓存思想 若缓存中存在，则直接从缓存数据库中查询数据并返回 若缓存中不存在，则到关系型数据库中查询，将查询到的结果放入缓存并返回 二、Redis数据库 1. 下载 注意：官网并不支持下载Windows版本的Redis，所以微软出面制作了Windows版本的Redis，但是自16年开始更新到版本3.2.100就不在更新了，无奈，一群自愿者出来开始更新Windows版本的Redis 微软的：https://github.com/microsoftarchive/redis 自愿者的：https://github.com/tporadowski/redis 2. 安装 下载zip压缩包，解压即可使用 3. 配置 redis.windows.conf：Redis的核心配置文件 redis-cli.exe：Redis的客户端 redis-server.exe：Redis的服务端 4. 使用 双击redis-server.exe运行服务端 双击redis-cli.exe运行客户端 5. Redis的学习 Redis数据结构 key-value格式的数据 key都是字符串 value有5种不同的数据结构 字符串类型：string（String） 哈希类型：hash（map） 列表类型：list（linkedlist） 集合类型：set（无重复值） 有序集合类型：sortedset（有序且无重复值） 6. 字符串类型 存储：set key value 获取：get key 删除：del key 7. 哈希类型 存储：hset key field value 获取：hget key field hgetall key 删除：hdel key field 8. 列表类型（可重复） 可以添加一个元素到列表的头部或者尾部 添加 lpush key value：将元素加到列表头部 rpush key value：将元素加到列表尾部 获取 lrange key start end：获取范围内的列表元素 lrange key 0 -1：获取列表中所有元素 删除 lpop key：从列表的头部删除元素，并将元素返回 rpop key：从列表的尾部删除元素，并将元素返回 9. 集合类型（无重复） 存储：sadd key value 获取：smembers key：获取set集合中所有元素 删除：srem key value：删除set集合中的某个元素 10. 有序集合类型（有序无重复） 每个value对应一个double类型的浮点数，有序集合正是通过这个浮点数保证有序的 存储：zadd key score value 获取：zrange key start end zrange key 0 -1 withscores 删除：zrem key value 11. 通用命令 查询所有的键：key * 查询指定键对应value的类型：type key 删除指定的key-value：del key 12. 持久化 将内存中的数据存储到本地，就叫持久化数据 Redis持久化机制 RDB：默认方式，不需要配置，默认就使用这种机制 在一定时间间隔中，检测key的变化情况，然后持久化数据 AOF：日志记录的方式，可以记录每一条命令的操作，可以在每一次命令操作后，持久化数据 配置文件 redis.windows.conf RDB配置 修改以下配置文件项 save 900 1 # after 900 sec (15 min) if at least 1 key changed save 300 10 # after 300 sec (5 min) if at least 10 keys changed save 60 10000 # after 60 sec if at least 10000 keys changed 重新启动 Redis，并指定配置文件：redis-server.exe redis.windows.conf AOF配置 修改以下配置文件项 appendonly no # 关闭AOF appendonly yes # 开启AOF appendfsync always # 每一次操作都进行持久化 appendfsync everysec # 每隔一秒进行一次持久化 appendfsync no # 不进行持久化 重新启动Redis，并指定配置文件： redis-server.exe redis.windows.conf 三、Java操作Redis 1. Jedis 导入maven依赖 &lt;!-- https://mvnrepository.com/artifact/redis.clients/jedis --&gt; &lt;dependency&gt; &lt;groupId&gt;redis.clients&lt;/groupId&gt; &lt;artifactId&gt;jedis&lt;/artifactId&gt; &lt;version&gt;3.3.0&lt;/version&gt; &lt;/dependency&gt; 代码实现 public class JedisDemo1 { public static void main(String[] args) { Jedis jedis = new Jedis(&quot;localhost&quot;,6379); jedis.set(&quot;password&quot;,&quot;123456&quot;); jedis.close(); } } 前往客户端查看 keys *，可以看到password的key和值 2. Jedis操作5种Redis数据结构 操作String类型 public class JedisDemo1 { public static void main(String[] args) { Jedis jedis = new Jedis(&quot;localhost&quot;,6379); //操作String类型（String） jedis.set(&quot;password&quot;,&quot;123456&quot;); //操作哈希类型（HashMap） HashMap&lt;String, String&gt; hashmap = new HashMap&lt;String, String&gt;(); hashmap.put(&quot;name&quot;,&quot;zhangsan&quot;); hashmap.put(&quot;age&quot;,&quot;69&quot;); hashmap.put(&quot;gender&quot;,&quot;male&quot;); jedis.hset(&quot;hashmap&quot;,hashmap); //操作列表类型（list） jedis.lpush(&quot;listB&quot;,&quot;a&quot;); jedis.lpush(&quot;listB&quot;,&quot;b&quot;); jedis.rpush(&quot;listB&quot;,&quot;c&quot;); //操作Set集合（set） jedis.sadd(&quot;set&quot;,&quot;a&quot;); jedis.sadd(&quot;set&quot;,&quot;b&quot;); //操作有序集合（SortedSet） jedis.zadd(&quot;zset&quot;,99,&quot;zhangsan&quot;); jedis.zadd(&quot;zset&quot;,69,&quot;lisi&quot;); jedis.zadd(&quot;zset&quot;,66,&quot;wangwu&quot;); jedis.close(); } } 3. String类型常用方法 setex(String key,int second,String value)：增加字符串数据项并指定过期时间 set(String key,String value)：增加字符串数据项（覆盖，已存在覆盖值） setnx(String key,String value)：增加字符串数据项（不覆盖，若已存在则不执行） del(String key)：删除指定键的字符串数据项 get(String key)：获取指定键的字符串数据项的值 append(String key,String s)：在指定键的字符串数据项的value后追加字符串 mset(String key1,String value1,String key2,String value2...)：增加多条字符串数据项 mget(String key1,String key2...)：获取指定键对应的多条数据项 4. Hash类型常用方法 hset(String key,String key,String value)：增加指定键的hash数据项 hset(String key,Map map)：增加指定键对应的值加入hash数据项 hgetAll(String key)：获取指定键对应的hash数据项 hkeys(String key)：获取指定键对应的hash数据项的所有键 hvals(String key)：获取指定键对应的hash数据项的所有值 hdel(String key,String k1,String k2...)：从指定键对应的hash数据项中删除多个元素 hlen(String key)：获取指定键对应的hash数据项的元素个数 hexists(String key,String k1)：判断指定键对应的hash数据相中是否存在k1对应的元素 5. List类型常用方法 lpush(String key,String v1,String v2...)：从list头部插入元素 rpush(String key,String v1,String v2...)：从list尾部插入元素 lrange(String key,int start,int end)：获取指定区间的list元素 lrem(String key,int n,String value)： lpop(String key)：从list头部弹出一个元素 rpop(String key)：从list尾部弹出一个元素 llen(String key)：获取list的元素个数 lset(String key,int index,String value)：修改list中指定索引处的值 6. Set类型常用方法 sadd(String key,String v1,Stirng v2...)：增加指定键对应的set数据项 smembers(String key)：获取指定键对应的set数据项的所有元素 srem(String key,String val)：删除set中值为val的数据项 srem(String key,String v1,String v2...)：删除set中值为v1，v2的数据项、 spop(String key)：随机弹出set中一个元素 scard(String key)：获取set集合的元素个数 7. SortedSet类型常用方法 zadd(String key,Map map)：增加指定件对应的zset数据项 zadd(String key,int score,int val)：向zset中加入一个元素 zrange(String key,int start,int end)：获取zset指定区间的元素值 zrangeWithScore(String key,int start,int end)：获取zset指定区间的元素值和score zrangeByScore(String key,int start,int end) zrem(String key,String vlaue)：删除zset例指定值的元素 zcard(String key)：获取zset中元素的个数 四、Jedis连接池 1. JedisPool JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(50); config.setMaxIdle(10); config.setMaxWaitMillis(3000); JedisPool jedisPool = new JedisPool(config,&quot;localhost&quot;,6379); Jedis jedis = jedisPool.getResource(); jedis.set(&quot;key&quot;,&quot;value&quot;); jedis.close(); 2. JedisPool工具类 package com.zhiyuan.jedis; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.io.IOException; import java.util.Properties; public class JedisPoolUtils { private static JedisPool jedisPool; private JedisPoolUtils(){} static{ Properties pros = new Properties(); try { pros.load(JedisPoolUtils.class.getClassLoader().getResourceAsStream(&quot;jedis.properties&quot;)); String host = pros.getProperty(&quot;Host&quot;); String port = pros.getProperty(&quot;Port&quot;); String maxTotal = pros.getProperty(&quot;MaxTotal&quot;); String maxIdle = pros.getProperty(&quot;MaxIdle&quot;); String maxWaitMillis = pros.getProperty(&quot;MaxWaitMillis&quot;); JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(Integer.parseInt(maxIdle)); config.setMaxTotal(Integer.parseInt(maxTotal)); config.setMaxWaitMillis(Integer.parseInt(maxWaitMillis)); jedisPool = new JedisPool(config,host, Integer.parseInt(port)); } catch (IOException e) { e.printStackTrace(); } } public static Jedis getJedis(){ return jedisPool.getResource(); } } Host=localhost Port=6379 MaxTotal=50 MaxIdle=10 MaxWaitMillis=3000 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/WvJdcATYk/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/WvJdcATYk/"},{"title":"JS原生实现简单ajax请求","content":"一、写个简单的HTML页面 &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;Ajax请求测试&lt;/title&gt; &lt;link rel=&quot;stylesheet&quot; href=&quot;http://at.alicdn.com/t/font_2335994_6js183z4xv2.css&quot;&gt; &lt;script src=&quot;./js/jquery-3.1.1.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div class=&quot;container&quot;&gt; &lt;div class=&quot;search&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;请输入关键字查询&quot;&gt; &lt;button&gt; &lt;i class=&quot;iconfont icon-sousuo&quot;&gt;&lt;/i&gt; 搜索 &lt;/button&gt; &lt;/div&gt; &lt;div class=&quot;search-info&quot;&gt; &lt;ul class=&quot;search-list&quot;&gt;&lt;/ul&gt; &lt;/div&gt; &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; 二、随便写点CSS &lt;style&gt; *{ margin:0; padding:0; box-sizing: border-box; } li{ list-style:none; } a{ text-decoration:none; } .container{ width: 800px; height: auto; background-color:#f2f2f2; margin:50px auto; overflow: hidden; } .search{ width: 600px; height: 50px; line-height: 50px; background-color:#228b22; margin:50px auto auto; display: flex; flex-direction:row; justify-content: space-between; } .search input{ flex:10; border:1px solid #228b22; border-right:none; outline:none; text-indent:20px; } .search button{ flex:2; font-size: 14px; color:#228b22; border:1px solid #228b22; outline:none; background-color: #fff; } .search-info{ display:none; width:600px; height:auto; background-color:#fff; margin:0 auto; border:1px solid #228b22; border-top:none; } ul{ padding:20px; } ul &gt; li{ margin:0 0 10px 0; font-size:14px; line-height: 14px; color:#666666; } &lt;/style&gt; 三、JS原生实现Ajax 问：这不是用了JQuery吗，扯什么原生？ 答：DOM元素的处理使用了JQuery，因为更方便，只是为了简化JS操作DOM的步骤，AJAX还是JS原生的 &lt;script&gt; //获取页面中的输入框元素并绑定oninput和onpropertychange事件，实现对input内容的实时监控 $('input').bind('input propertychange', function(){ if(!($('input').val()==&quot;&quot;)){ //初始化xhttp对象为null let xhttp = null; if(window.XMLHttpRequest){ //现代浏览器的创建XMLHttp对象的方式（IE7+） xhttp = new XMLHttpRequest(); }else{ //非现代浏览器创建XMLHttp对象的方式（IE7-） xhttp = new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;); } //判断xhttp对象获取成功，执行以下操作 if(xhttp!=null){ // 监听ajax请求就绪事件 xhttp.onreadystatechange = function(){ // 请求就绪，响应正确，就开始解析返回的数据 if(xhttp.readyState===4 &amp;&amp; xhttp.status===200){ // 将返回的字符串数据解析为JSON对象 let responseData = JSON.parse(xhttp.responseText); // 每次重新请求的同时，移除列表中所有li元素 $('.search-list').find(&quot;li&quot;).remove(); for (const filminfo of responseData) { // 向ul列表中追加子元素li $('.search-list').append(&quot;&lt;li class=\\&quot;list-item\\&quot;&gt;&quot;+filminfo[&quot;name&quot;]+&quot;&lt;/li&gt;&quot;); console.log(&quot;&lt;li class=\\&quot;list-item\\&quot;&gt;&quot;+filminfo[&quot;name&quot;]+&quot;&lt;/li&gt;&quot;); } // 由上到下展开显示列表 $('.search-info').slideDown(300); } } // 建立异步请求的链接 xhttp.open(&quot;GET&quot;,&quot;/360Video/demo1?keyword=&quot;+$('input').val(),true); // 发送异步请求 xhttp.send(); } } }); // 输入框失焦，隐藏列表 $('input').blur(function(){ $('.search-info').slideUp(300); }); &lt;/script&gt; 四、写个简单的Servlet去响应ajax请求 @WebServlet(urlPatterns = &quot;/demo1&quot;) public class ServletDemo1 extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String keyword = request.getParameter(&quot;keyword&quot;); System.out.println(keyword); // 这里我使用自己的工具类去拿了360影视的数据，以json格式返回 String data = HttpUtils.getData(&quot;https://www.360kan.com/dianying/list.php?rank=rankhot&amp;cat=all&amp;area=all&amp;act=all&amp;year=all&amp;pageno=2&amp;from=dianying_list&quot;); // 这里必须要这么设置Content-Type，不然前端获取到的json里面中文是乱码的 response.setContentType(&quot;application/json;charset=UTF-8&quot;); // 将json数据写出到页面 response.getWriter().write(data); } } 五、最终效果展示 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/3g0TLSILu/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/3g0TLSILu/"},{"title":"Java原生实现DOM树解析XML文件","content":"一、测试用的XML文件 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;bookstore&gt; &lt;book id=&quot;1&quot;&gt; &lt;name&gt;书1&lt;/name&gt; &lt;author&gt;作者1&lt;/author&gt; &lt;price&gt;￥998&lt;/price&gt; &lt;/book&gt; &lt;book id=&quot;2&quot;&gt; &lt;name&gt;书2&lt;/name&gt; &lt;author&gt;作者2&lt;/author&gt; &lt;price&gt;￥998&lt;/price&gt; &lt;/book&gt; &lt;book id=&quot;3&quot;&gt; &lt;name&gt;书3&lt;/name&gt; &lt;author&gt;作者3&lt;/author&gt; &lt;price&gt;￥998&lt;/price&gt; &lt;/book&gt; &lt;/bookstore&gt; 二、解析XML文件 public class DomDemo { public static void main(String[] args) { InputStream inputStream = DomDemo.class.getClassLoader().getResourceAsStream(&quot;test.xml&quot;); DocumentBuilderFactory dom = DocumentBuilderFactory.newInstance(); try { DocumentBuilder domBuilder = dom.newDocumentBuilder(); Document document = domBuilder.parse(inputStream); Element bookstore = document.getDocumentElement(); System.out.println(bookstore.getTagName()); NodeList bookList = bookstore.getElementsByTagName(&quot;book&quot;); for (int i = 0; i &lt; bookList.getLength() ; i++) { Node book = bookList.item(i); NamedNodeMap bookAttributes = book.getAttributes(); for (int j = 0; j &lt; bookAttributes.getLength(); j++) { Node node = bookAttributes.item(j); if (node.getNodeType() == Node.ATTRIBUTE_NODE){ System.out.println(node.getNodeName()+&quot;:&quot;+node.getNodeValue()); } } NodeList bookChildList = book.getChildNodes(); for (int j = 0; j &lt; bookChildList.getLength(); j++) { Node childItem = bookChildList.item(j); if (childItem.getNodeType() == Node.ELEMENT_NODE){ System.out.println(childItem.getNodeName()+&quot;:&quot;+childItem.getTextContent()); } } System.out.println(&quot;=======================&quot;); } } catch (Exception e) { e.printStackTrace(); } } } 三、稍微有点麻烦了，建议使用Jsoup，操作更加方便 bookstore id:1 name:书1 author:作者1 price:￥998 ======================= id:2 name:书2 author:作者2 price:￥998 ======================= id:3 name:书3 author:作者3 price:￥998 ======================= 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/fIrwGdZp7/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/fIrwGdZp7/"},{"title":"JSTL标签+EL表达式改进360影视采集练习案例","content":"一、导入JSTL标签库和EL表达式的依赖 &lt;dependency&gt; &lt;groupId&gt;javax.servlet.jsp.jstl&lt;/groupId&gt; &lt;artifactId&gt;jstl&lt;/artifactId&gt; &lt;version&gt;1.2&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;javax.el&lt;/groupId&gt; &lt;artifactId&gt;javax.el-api&lt;/artifactId&gt; &lt;version&gt;3.0.0&lt;/version&gt; &lt;/dependency&gt; 如果你们创建的不是maven项目，那么可能需要自己去tomcat的lib目录下去找这两个JAR包，然后导入到项目中 二、改进之前的JSP页面 &lt;%@ page import=&quot;com.zhiyuan.utils.HttpUtils&quot; %&gt; &lt;%@ page import=&quot;com.google.gson.JsonArray&quot; %&gt; &lt;%@ page import=&quot;com.google.gson.JsonParser&quot; %&gt; &lt;%@ page import=&quot;com.zhiyuan.bean.FilmInfo&quot; %&gt; &lt;%@ page import=&quot;java.util.ArrayList&quot; %&gt; &lt;%@ page import=&quot;com.google.gson.Gson&quot; %&gt; &lt;%@ page import=&quot;com.google.gson.JsonElement&quot; %&gt; &lt;%@ page contentType=&quot;text/html;charset=UTF-8&quot; language=&quot;java&quot; pageEncoding=&quot;UTF-8&quot; isELIgnored=&quot;false&quot; %&gt; &lt;%@ taglib prefix=&quot;c&quot; uri=&quot;http://java.sun.com/jsp/jstl/core&quot; %&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;360Video&lt;/title&gt; &lt;link rel=&quot;icon&quot; href=&quot;https://www.360kan.com/favicon.ico&quot;&gt; &lt;link rel=&quot;stylesheet&quot; href=&quot;./css/style.css?v=&lt;%=Math.random()%&gt;&quot;&gt; &lt;script src=&quot;./js/jquery-3.1.1.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div class=&quot;header&quot;&gt; &lt;div class=&quot;nav&quot;&gt;360影视采集&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;container&quot;&gt; &lt;ul class=&quot;list&quot;&gt; &lt;% String data = HttpUtils.getData(&quot;https://www.360kan.com/dianying/list.php?rank=rankhot&amp;cat=all&amp;area=all&amp;act=all&amp;year=all&amp;pageno=2&amp;from=dianying_list&quot;); JsonArray jsonElements = new JsonParser().parse(data).getAsJsonArray(); ArrayList&lt;FilmInfo&gt; filmInfos = new ArrayList&lt;&gt;(); Gson gson = new Gson(); for (JsonElement filmInfo : jsonElements) { filmInfos.add(gson.fromJson(filmInfo,FilmInfo.class)); } request.setAttribute(&quot;data&quot;,filmInfos); %&gt; &lt;c:forEach items=&quot;${requestScope.data}&quot; var=&quot;film&quot;&gt; &lt;li&gt; &lt;div class=&quot;poster&quot;&gt; &lt;img src=&quot;${film.poster}&quot;&gt; &lt;c:if test=&quot;${'付费'.equals(film.paid)}&quot;&gt; &lt;div class=&quot;paid&quot;&gt;付费&lt;/div&gt; &lt;/c:if&gt; &lt;div class=&quot;info&quot;&gt; &lt;div class=&quot;year&quot;&gt;${film.year}&lt;/div&gt; &lt;c:if test=&quot;${film.score!=null}&quot;&gt; &lt;div class=&quot;score&quot;&gt;${film.score}&lt;/div&gt; &lt;/c:if&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;detail&quot;&gt; &lt;div class=&quot;name&quot;&gt;${film.name}&lt;/div&gt; &lt;div class=&quot;star&quot;&gt;${film.star.length()&gt;12?film.star.substring(0,12).concat(&quot;...&quot;):film.star}&lt;/div&gt; &lt;/div&gt; &lt;/li&gt; &lt;/c:forEach&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; 注意：在页面中若想使用JSTL标签，必须要引入JSTL标签库，EL表达式不需要引入，可以直接使用 &lt;%@ taglib prefix=&quot;c&quot; uri=&quot;http://java.sun.com/jsp/jstl/core&quot; %&gt; 三、实际效果和原来还是一模一样的 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/AqFeWp4vE/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/AqFeWp4vE/"},{"title":"JavaWeb阶段练习：360影视数据采集","content":"一、idea创建Maven管理的JavaWeb项目 二、创建一个用来封装影片数据的JavaBean对象 public class FilmInfo { private String name; private String url; private String poster; private boolean isPaid; private String year; private String score; private String star; public FilmInfo(){} public FilmInfo(String name, String url, String poster, boolean isPaid, String year, String score, String star) { this.name = name; this.url = url; this.poster = poster; this.isPaid = isPaid; this.year = year; this.score = score; this.star = star; } public String getName() { return name; } public String getUrl() { return url; } public String getPoster() { return poster; } public boolean isPaid() { return isPaid; } public String getYear() { return year; } public String getScore() { return score; } public String getStar() { return star; } public void setName(String name) { this.name = name; } public void setUrl(String url) { this.url = url; } public void setPoster(String poster) { this.poster = poster; } public void setPaid(boolean paid) { isPaid = paid; } public void setYear(String year) { this.year = year; } public void setScore(String score) { this.score = score; } public void setStar(String star) { this.star = star; } @Override public String toString() { final StringBuffer sb = new StringBuffer(&quot;FilmInfo{&quot;); sb.append(&quot;name='&quot;).append(name).append('\\''); sb.append(&quot;, url='&quot;).append(url).append('\\''); sb.append(&quot;, poster='&quot;).append(poster).append('\\''); sb.append(&quot;, isPaid=&quot;).append(isPaid); sb.append(&quot;, year=&quot;).append(year); sb.append(&quot;, score=&quot;).append(score); sb.append(&quot;, star='&quot;).append(star).append('\\''); sb.append('}'); return sb.toString(); } } 三、使用了 Gson 和 HttpUtils 的依赖 1. Gson的maven依赖 &lt;dependency&gt; &lt;groupId&gt;com.google.code.gson&lt;/groupId&gt; &lt;artifactId&gt;gson&lt;/artifactId&gt; &lt;version&gt;2.8.5&lt;/version&gt; &lt;/dependency&gt; 2. HttpUtils是我手写的Http工具类，里面主要包含3个方法，get请求，post请求，还有采集数据的getData方法 public class HttpUtils { public static String getHtmlCode(String url) { HttpURLConnection connection = null; InputStream is = null; BufferedReader br = null; StringBuilder sb = new StringBuilder(); try { connection = (HttpURLConnection) new URL(url).openConnection(); is = connection.getInputStream(); br = new BufferedReader(new InputStreamReader(is, &quot;UTF-8&quot;)); String line; while ((line = br.readLine()) != null) { sb.append(line); sb.append(&quot;\\n&quot;); } } catch (IOException e) { e.printStackTrace(); } return sb.toString(); } public static String Get(String url) { HttpURLConnection conn = null; InputStream is = null; BufferedReader br = null; String line = null; StringBuilder sb = new StringBuilder(); try { //获取URL连接对象强转为HTTPURL连接对象 conn = (HttpURLConnection) new URL(url).openConnection(); //设置HTTP请求方式为GET conn.setRequestMethod(&quot;GET&quot;); //设置连接超时时间为3秒 conn.setConnectTimeout(3000); //设置读取时间超时为5秒 conn.setReadTimeout(5000); conn.setRequestProperty(&quot;Content-Type&quot;, &quot;text/html;charset=utf-8&quot;); conn.setRequestProperty(&quot;Accept&quot;, &quot;text/html,application/xhtml+xml,application/xml,application/json;&quot;); conn.setRequestProperty(&quot;User-Agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36&quot;); //发送请求 conn.connect(); if (conn.getResponseCode() == 200) { is = conn.getInputStream(); br = new BufferedReader(new InputStreamReader(is, &quot;UTF-8&quot;)); while ((line = br.readLine()) != null) { sb.append(line + &quot;\\n&quot;); } } else { return &quot;ResponseCode is Error:&quot; + conn.getResponseCode(); } } catch (IOException e) { e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } public static String Post(String url, String requestContent) { System.out.println(&quot;请求的URL：&quot;+url); System.out.println(&quot;请求的参数：&quot;+requestContent); HttpURLConnection conn = null; OutputStream os = null; BufferedWriter bw = null; InputStream is = null; String line = null; StringBuilder sb = new StringBuilder(); BufferedReader br = null; try { conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestProperty(&quot;Connection&quot;,&quot;keep-alive&quot;); conn.setRequestMethod(&quot;POST&quot;); conn.setDoInput(true); conn.setDoOutput(true); conn.setConnectTimeout(3000); conn.setReadTimeout(15000); conn.setRequestProperty(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded;charset=utf-8&quot;); conn.setRequestProperty(&quot;Accept&quot;, &quot;text/html,application/xhtml+xml,application/xml,application/json,application/x-www-form-urlencoded&quot;); conn.setRequestProperty(&quot;User-Agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36&quot;); conn.getOutputStream().write(requestContent.getBytes(&quot;UTF-8&quot;)); conn.getOutputStream().flush(); if (conn.getResponseCode()==200){ is = conn.getInputStream(); br = new BufferedReader(new InputStreamReader(is, &quot;UTF-8&quot;)); while((line=br.readLine())!=null){ sb.append(line+&quot;\\n&quot;); } }else{ return &quot;ResponseCode is Error:&quot; + conn.getResponseCode(); } } catch (IOException e) { e.printStackTrace(); }finally { if(br!=null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(bw!=null){ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } public static String getData(String url){ String sourceCode = Get(url); String regex = &quot;&lt;li class=\\&quot;item\\&quot;&gt;\\\\s*&lt;a class=\\&quot;js-tongjic\\&quot; href=\\&quot;(.*?)\\&quot; monitor-shortpv-c=\\&quot;(.*?)\\&quot;&gt;\\\\s*&lt;div class=\\&quot;cover g-playicon\\&quot;&gt;\\\\s*&lt;img src=\\&quot;(.*?)\\&quot;&gt;\\\\s*(&lt;span class=\\&quot;pay\\&quot;&gt;(.*?)&lt;/span&gt;)?\\\\s*&lt;div class=\\&quot;mask-wrap\\&quot;&gt;\\\\s*&lt;span class=\\&quot;hint\\&quot;&gt;(.*?)&lt;/span&gt;\\\\s*(&lt;span class=\\&quot;point\\&quot;&gt;(.*?)&lt;/span&gt;)?\\\\s*&lt;/div&gt;\\\\s*&lt;/div&gt;\\\\s*&lt;div class=\\&quot;detail\\&quot;&gt;\\\\s*&lt;p class=\\&quot;title g-clear\\&quot;&gt;\\\\s*&lt;span class=\\&quot;s1\\&quot;&gt;(.*?)&lt;/span&gt;\\\\s*&lt;/p&gt;\\\\s*&lt;p class=\\&quot;star\\&quot;&gt;(.*?)&lt;/p&gt;\\\\s*&lt;/div&gt;\\\\s*&lt;/a&gt;\\\\s*&lt;/li&gt;&quot;; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(sourceCode); // System.out.println(m.groupCount()); Gson gson = new GsonBuilder().setPrettyPrinting().create(); ArrayList&lt;FilmInfo&gt; list = new ArrayList&lt;FilmInfo&gt;(); while(m.find()){ FilmInfo fi = new FilmInfo(m.group(9),&quot;http://www.360kan.com/&quot;+m.group(1),m.group(3),&quot;付费&quot;.equals(m.group(5)),m.group(6),m.group(8),m.group(10)); list.add(fi); } return gson.toJson(list); } } Java实现原生的get和post请求，我在之前的博客中发过了，不懂的朋友可以回看我之前的博客，我就不做解释了，这里解释一下getData方法，这个方法主要是使用get发送请求获取指定页面源码，再通过正则表达式拿到我们所需要的数据，使用gson封装为Json数据返回，没有什么技术含量 注意：为了取到详细信息，此处正则表达式比较长，我建议使用配置文件来读取这种较长的正则表达式，避免代码中语句过长，问题排查难度大的问题，我这里是做记录写博客，所以直接放到变量里 四、随便写个前端页面 1. JSP部分 &lt;%@ page import=&quot;com.zhiyuan.utils.HttpUtils&quot; %&gt; &lt;%@ page import=&quot;com.google.gson.JsonArray&quot; %&gt; &lt;%@ page import=&quot;com.google.gson.JsonParser&quot; %&gt; &lt;%@ page import=&quot;com.zhiyuan.bean.FilmInfo&quot; %&gt; &lt;%@ page import=&quot;java.util.ArrayList&quot; %&gt; &lt;%@ page import=&quot;com.google.gson.Gson&quot; %&gt; &lt;%@ page import=&quot;com.google.gson.JsonElement&quot; %&gt; &lt;%@ page contentType=&quot;text/html;charset=UTF-8&quot; language=&quot;java&quot; pageEncoding=&quot;UTF-8&quot; %&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;360Video&lt;/title&gt; &lt;link rel=&quot;icon&quot; href=&quot;https://www.360kan.com/favicon.ico&quot;&gt; &lt;link rel=&quot;stylesheet&quot; href=&quot;./css/style.css?v=&lt;%=Math.random()%&gt;&quot;&gt; &lt;script src=&quot;./js/jquery-3.1.1.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div class=&quot;header&quot;&gt; &lt;div class=&quot;nav&quot;&gt;360影视采集&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;container&quot;&gt; &lt;ul class=&quot;list&quot;&gt; &lt;% String data = HttpUtils.getData(&quot;https://www.360kan.com/dianying/list.php?rank=rankhot&amp;cat=all&amp;area=all&amp;act=all&amp;year=all&amp;pageno=2&amp;from=dianying_list&quot;); JsonArray jsonElements = new JsonParser().parse(data).getAsJsonArray(); ArrayList&lt;FilmInfo&gt; filmInfos = new ArrayList&lt;&gt;(); Gson gson = new Gson(); for (JsonElement filmInfo : jsonElements) { filmInfos.add(gson.fromJson(filmInfo,FilmInfo.class)); } for (FilmInfo filmInfo : filmInfos) { %&gt; &lt;li&gt; &lt;div class=&quot;poster&quot;&gt; &lt;img src=&quot;&lt;%=filmInfo.getPoster()%&gt;&quot;&gt; &lt;div class=&quot;paid&quot;&gt;&lt;%=filmInfo.isPaid()?&quot;付费&quot;:&quot;免费&quot;%&gt;&lt;/div&gt; &lt;div class=&quot;info&quot;&gt; &lt;div class=&quot;year&quot;&gt;&lt;%=filmInfo.getYear()%&gt;&lt;/div&gt; &lt;div class=&quot;score&quot;&gt;&lt;%=filmInfo.getScore()!=null?filmInfo.getScore():&quot;&quot;%&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;detail&quot;&gt; &lt;div class=&quot;name&quot;&gt;&lt;%=filmInfo.getName()%&gt;&lt;/div&gt; &lt;div class=&quot;star&quot;&gt;&lt;%=filmInfo.getStar().length()&gt;12?filmInfo.getStar().substring(0,12)+&quot;...&quot; : filmInfo.getStar()%&gt;&lt;/div&gt; &lt;/div&gt; &lt;/li&gt; &lt;% } %&gt; &lt;/ul&gt; &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; 2. css部分 *{ margin: 0; padding: 0; box-sizing: border-box; } body,html{ background-color:#f2f2f2; } a{ text-decoration:none; } li{ list-style:none; } .header{ width: 100%; height: 50px; line-height: 50px; text-align: center; color:#228b22; font-weight: bold; background-color:#fff; box-shadow:rgba(0, 0, 0, 0.2) 1px 1px 8px 1px; } .container{ width:1190px; background-color:#fff; margin:20px auto; } .list &gt; li{ display: flex; flex-direction:column; align-items:center; width: 155px; height: 250px; margin-bottom:15px; } .list{ display:flex; flex-direction: row; flex-wrap: wrap; justify-content:space-between; } li &gt; .poster{ width: 155px; height: 212px; border-radius:5px; overflow: hidden; position:relative; } .poster &gt; img{ width: 100%; height: 100%; } .poster .info{ position:absolute; width:155px; height: 38px; line-height: 38px; font-size: 12px; color:#000; bottom:0; background-color:rgba(0,0,0,.12); display: flex; flex-direction: row; justify-content:space-between; padding:0 10px; } .poster .paid{ position: absolute; left:5px; top:5px; width:40px; height: 20px; line-height: 20px; font-size: 12px; color:#fff; background-color:goldenrod; text-align:center; border-radius:5px; } .info .score{ color:goldenrod; } .info .year{ color:#fff; } li &gt; .detail{ width: 155px; height: 38px; display: flex; flex-direction: column; justify-content: center; align-items:center; cursor:pointer; } .detail &gt; .name{ width:155px; height: 14px; line-height: 14px; font-size: 14px; color:#000; padding-left:5px; } .detail &gt; .star{ width: 155px; height: 12px; line-height: 12px; font-size: 12px; color:#666666; margin-top:5px; padding-left:5px; } 五、运行起来看一下效果 这里我只是采集了1页数据，当然采集其他的数据不过是重复这个操作，没有什么技术含量，就不说了 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/360Video/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/360Video/"},{"title":"LayUI学习（2）栅格系统","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; href=&quot;layui-v2.5.7/layui/css/layui.css&quot;&gt; &lt;script src=&quot;layui-v2.5.7/layui/layui.js&quot;&gt;&lt;/script&gt; 二、栅格系统的布局容器 layui-container 建议使用layui-container 作为包裹栅格系统的容器，这样可以让列在不同尺寸屏幕下可控 &lt;div class=&quot;layui-container&quot;&gt;&lt;/div&gt; 三、栅格系统的行容器 layui-row &lt;div class=&quot;layui-row&quot;&gt;&lt;/div&gt; 四、栅格系统的行内空间分配 layui-col-md列数 在LayUI中将栅格系统分为12格，也就是说每一行会有12列的空间供我们调节内容布局，超过12列自动换行，栅格系统中的内容会根据所在屏幕尺寸完成自适应 列可以同时出现最多四种不同的组合，分别是：xs（超小屏幕，如手机）、sm（小屏幕，如平板）、md（桌面中等屏幕）、lg（桌面大型屏幕），以呈现更加动态灵活的布局。 本教程仅以中等屏幕（md）为例展示栅格系统用法 &lt;div class=&quot;layui-row&quot;&gt; &lt;div class=&quot;layui-col-md5&quot;&gt; 占宽度的十二分之五 &lt;/div&gt; &lt;div class=&quot;layui-col-md7&quot;&gt; 占宽度的十二分之七 &lt;/div&gt; &lt;/div&gt; 我们通过 layui-col-md列数 的格式可以为指定div分配指定列数的空间 五、为栅格系统设置列间距 layui-col-space像素 LayUI的layui-col-space像素像素支持范围为1px~30px &lt;div class=&quot;layui-row layui-col-space10&quot;&gt; &lt;div class=&quot;layui-col-md4&quot;&gt; 占宽度的十二分之四 &lt;/div&gt; &lt;div class=&quot;layui-col-md4&quot;&gt; 占宽度的十二分之四 &lt;/div&gt; &lt;div class=&quot;layui-col-md4&quot;&gt; 占宽度的十二分之四 &lt;/div&gt; &lt;/div&gt; 六、为栅格系统设置列偏移 layui-col-md-offset列数 &lt;div class=&quot;layui-row&quot;&gt; &lt;div class=&quot;layui-col-md4&quot;&gt; 占宽度的十二分之四 &lt;/div&gt; &lt;div class=&quot;layui-col-md4 layui-col-md-offset4&quot;&gt; 占宽度的十二分之四，但是栅格偏移四列，所以当前div会偏移到最右侧 &lt;/div&gt; &lt;/div&gt; 七、栅格嵌套 &lt;div class=&quot;layui-container&quot;&gt; &lt;div class=&quot;layui-row layui-col-space10&quot;&gt; &lt;div class=&quot;layui-col-md6&quot;&gt; &lt;div class=&quot;layui-col-md4&quot; style=&quot;background-color:#3ace72;&quot;&gt;十二分之四&lt;/div&gt; &lt;div class=&quot;layui-col-md8&quot; style=&quot;background-color:#00aae7;&quot;&gt;十二分之八&lt;/div&gt; &lt;div class=&quot;layui-col-md8&quot; style=&quot;background-color:#666666;&quot;&gt;十二分之八&lt;/div&gt; &lt;div class=&quot;layui-col-md4&quot; style=&quot;background-color:#00F7DE;&quot;&gt;十二分之四&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;layui-col-md6&quot;&gt; &lt;div class=&quot;layui-col-md2&quot; style=&quot;background-color:#2E2D3C;&quot;&gt;十二分之二&lt;/div&gt; &lt;div class=&quot;layui-col-md10&quot; style=&quot;background-color:#3c4f6b;&quot;&gt;十二分之十&lt;/div&gt; &lt;div class=&quot;layui-col-md10&quot; style=&quot;background-color:#4DD89E;&quot;&gt;十二分之十&lt;/div&gt; &lt;div class=&quot;layui-col-md2&quot; style=&quot;background-color:#009f95;&quot;&gt;十二分之二&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/Dw2rPiHYV/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/Dw2rPiHYV/"},{"title":"JS正则实现Json文本压缩去除空格效果","content":"一、准备一段测试用的Json文本 { &quot;age&quot; : 12, &quot;name&quot; : &quot;zhiyuan&quot;, &quot;gender&quot; : &quot;male&quot;, &quot;skill&quot; : [&quot;J a v a&quot;,&quot;C&quot;,&quot;J S&quot;,&quot;Py th on&quot;] } 二、去除除value值中的空白字符之外的所有空白字符 function clearEmpty(){ var json = document.getElementById(&quot;content&quot;).value; console.log(json); let str = json.replace(&quot;\\r\\n&quot;,&quot;&quot;); let newStr = str.trim().replace(/\\s*\\[\\s*/g,&quot;\\[&quot;).replace(/\\s*\\]\\s*/g,&quot;\\]&quot;).replace(/\\s*\\{\\s*/g,&quot;\\{&quot;).replace(/\\s*\\}\\s*/g,&quot;\\}&quot;).replace(/&quot;\\s*:\\s*&quot;/g,'&quot;:&quot;').replace(/&quot;\\s*:\\s*/g,'&quot;:').replace(/&quot;\\s*,\\s*/g,'&quot;,').replace(/,\\s*&quot;/g,',&quot;').replace(/]\\s*,\\s*/g,&quot;],&quot;); console.log(newStr); document.getElementById(&quot;new-content&quot;).innerText = newStr; } 三、写一个难看的页面 &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; &lt;meta charset=&quot;UTF-8&quot;&gt; &lt;title&gt;Json文本格式压缩&lt;/title&gt; &lt;/head&gt; &lt;style&gt; *{ margin: 0; padding: 0; box-sizing: border-box; font-family:微软雅黑,sans-serif; } .container{ width: 100%; height:auto; background-color:#f8f8f8; padding:20px; position: relative; } #content,#new-content{ width: 100%; height:300px; background-color: #ffffff; color:#666; font-size:14px; outline:none; border:1px solid #1DB69A; border-radius:10px; padding:20px; } button{ background-color:#FFFFFF; color: #1DB69A; height: 50px; width: 150px; font-size: 18px; outline:none; border:1px solid #1DB69A; cursor: pointer; border-radius:50px; margin:20px; position: absolute; right:20px; top:250px; } button:hover{ background-color:#1DB69A; color: #FFFFFF; } button:focus{ background-color:#009688; color:#FFFFFF; } .line{ width:980px; margin:20px auto; font-size:20px; font-weight:bold; border-left:300px solid #1DB69A; border-right:300px solid #1DB69A; line-height: 2px; text-align: center; color: #1DB69A; } &lt;/style&gt; &lt;body&gt; &lt;div class=&quot;container&quot;&gt; &lt;div class=&quot;line&quot;&gt;请将json格式文本粘贴到文本框中&lt;/div&gt; &lt;textarea name=&quot;content&quot; cols=&quot;50&quot; rows=&quot;20&quot; id=&quot;content&quot;&gt;&lt;/textarea&gt; &lt;button onclick=&quot;clearEmpty()&quot;&gt;去除所有空格&lt;/button&gt; &lt;div class=&quot;line&quot;&gt;下面将生成去除空格的Json文本&lt;/div&gt; &lt;textarea name=&quot;new-content&quot; id=&quot;new-content&quot; cols=&quot;50&quot; rows=&quot;20&quot; disabled&gt;&lt;/textarea&gt; &lt;/div&gt; &lt;/body&gt; &lt;/html&gt; 四、测试一下效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/ZeuH4X3iy/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/ZeuH4X3iy/"},{"title":"CSS3新增选择器记录","content":"一、CSS3以前的选择器 选择器 概述 例 标签选择器 选择页面中所有指定类型的标签 a{} ID选择器 选择页面中拥有指定ID值的标签 #id{} Class选择器 选择页面中拥有指定Class值的标签 .class{} 通配符选择器 选择页面中所有标签 *{} 后代选择器 选择指定标签下所有后代元素标签 ul li{} 相邻兄弟选择器 选择指定标签同级的向下一个指定元素标签 li+li{} 子选择器 选择指定标签下指定类型的直接子元素标签 ul &gt; li{} 属性选择器 选择拥有指定属性和属性值的指定类型的标签 input[type='password']{} 分组选择器 同时选择多个满足条件的标签 html,body,p,ul{} 伪类选择器 一般指a标签等存在交互行为的元素在多种状态下的操作 a:hover{} a:active{} a:visited{} input:focus{} 伪元素选择器 DOM中不存在的元素，一般用来协助标签完成特定功能 div::before{} div::after{} p::first-line{} p::first-letter{} 过滤选择器 选择指定类型指定属性条件的标签 h1.title{} 二、CSS3新增的选择器 选择器 概述 例 加强属性选择器 在原有属性选择器的基础上增加了类似于字符串匹配的功能 input[name^='user']{} input[name$='word']{} input[name*='name']{} 结构伪类选择器 在之前特定几个伪类的基础上，添加了对子元素选择的支持 :root{} ul:nth-child(n){} ul:nth-last-child(n){} ul:nth-of-type(n){} ul:nth-last-of-type(n){} ul:last-child{} ul:first-child{} ul:first-of-type{} ul:last-of-type{} ul:only-child{} ul:only-of-type{} li:empty{} 否定伪类选择器 选择除满足指定选择器的元素外的所有标签元素 :not(p){} 通用兄弟选择器 选择指定元素之后的指定条件的多个同级标签元素 ul~li{} 目标伪类选择器 选择指定元素指向的锚点处的指定标签元素 #id:target{} 状态伪类选择器 选择拥有指定状态的标签元素 input:enabled{} input:disabled{} input:checked{} ::selection{} 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/ORutvrQMw/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/ORutvrQMw/"},{"title":"LayUI学习（1）布局容器","content":" 官网：https://www.layui.com/ 下载好LayUI的资源文件 一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; href=&quot;layui-v2.5.7/layui/css/layui.css&quot;&gt; &lt;script src=&quot;layui-v2.5.7/layui/layui.js&quot;&gt;&lt;/script&gt; 二、固定宽度容器 &lt;div class=&quot;layui-container&quot; style=&quot;background-color:#009F95;&quot;&gt; 固定宽度 &lt;/div&gt; 三、适应宽度容器 &lt;div class=&quot;layui-fluid&quot; style=&quot;background-color: #00FF00;&quot;&gt; 适应容器宽度 &lt;/div&gt; 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/8Jg7UIzu2/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/8Jg7UIzu2/"},{"title":"Java的代理模式实现","content":"一、代理模式 简介：用另外的对象处理当前对象的业务逻辑并且不会影响到当前对象的一种增强当前对象功能的开发模式 作用：临时增强当前对象的功能 1. 分类 静态代理 动态代理 2. 静态代理 问：什么是静态代理？ 答：通俗一点讲，就是我们在代码编写时已经已知了每一步的操作，然后一板一眼的去对指定的对象进行代理增强，这就是静态，当我们有新的需求，之前写的所有的代码都需要被改变 问：怎么实现？ 答：事实上，我们在SE中所学的 多态 就是静态代理模式的实现 问：多态？ 答：接口多态和抽象类多态正是我们去实现静态代理的方式 问：实现一下？ 答：好的，但我只实现接口多态，抽象类多态的写法也是一样的，需要你去思考 代码实现（接口多态实现） //1. 创建一个接口Person public interface Person { public abstract String buy(String name); } //2. 定义一个接口实现类实现Person接口 public class Customer implements Person{ @Override public String buy(String name){ return name + &quot;购买成功&quot;; } } //3. 定义代理类实现Person接口，并进行静态代理的实现 public class StaticProxyDemo implements Person{ private Person p; public StaticProxyDemo(Person p){ this.p = p; } @Override public String buy(String name) { return p.buy(name)+&quot;我增强了buy方法&quot;; } public static void main(String[] args) { Person p = new Customer(); StaticProxyDemo staticProxyDemo = new StaticProxyDemo(p); String result = staticProxyDemo.buy(&quot;橘子&quot;); System.out.println(result); } } 3. 动态代理 问：什么是动态代理？ 答：动态代理就是，我们已知要增强的功能，只需书写增强功能部分，然后通过程序运行过程在JVM内存中动态生成代理对象来实现对对象功能的增强 问：怎么实现？ 答：动态代理目前有两种实现方式，一种是JDK自带的 JDK代理（又叫接口代理） ，一种是使用第三方库 CGLIB 实现的 基于CGLIB的动态代理 问：实现一下？ 答：好的 JDK代理代码实现 依赖的类：java.lang.reflect.Proxy 使用的方法：static Object newProxyInstance(ClassLoader loader,Class&lt;?&gt;[] interfaces,InvocationHandler h) 方法参数讲解： ClassLoader loader：提供被代理类的类加载器 Class&lt;?&gt;[] interfaces：提供被代理类实现的接口 InvocationHandler h：提供增强对象的功能，它是一个接口，我们需要使用匿名内部类或者lambda表达式去实现它的 invoke 方法，来达到增强对象的目的 invoke方法参数详解 Object invoke(Object proxy, Method method, Object[] args) Object proxy：代理对象的引用，一般不使用 Method method：代理对象调用接口中方法触发的方法对象 Object[] args：代理对象调用接口中方法传递的参数列表 //创建一个接口Person public interface Person { public abstract String buy(String name); } //创建一个类实现接口Person public class Customer implements Person{ @Override public String buy(String name){ return name + &quot;购买成功&quot;; } } //创建测试类实现JDK动态代理 public class DynamicDemo { public static void main(String[] args) { //创建被代理对象 final Customer customer = new Customer(); Person customerProxy = (Person) Proxy.newProxyInstance(customer.getClass().getClassLoader(), customer.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (&quot;buy&quot;.equalsIgnoreCase(method.getName())) { Object result = method.invoke(customer, args); return result + &quot;，我被加强了&quot;; } else { return method.invoke(customer, args); } } }); String content = customerProxy.buy(&quot;橘子&quot;); System.out.println(content); } } CGLIB代理代码实现 依赖的包：我这里使用maven导入依赖 &lt;dependency&gt; &lt;groupId&gt;cglib&lt;/groupId&gt; &lt;artifactId&gt;cglib&lt;/artifactId&gt; &lt;version&gt;3.3.0&lt;/version&gt; &lt;/dependency&gt; 使用的类：net.sf.cglib.proxy.Enhancer 使用的方法：static Object create(Class type, Callback callback) 方法参数详解： Class type：提供被代理类的字节码文件对象 Callback callback：提供增强对象的功能，它是一个顶层接口，我们需要通过多态去实现它的子接口 MethodInterceptor并重写其中的intercept方法 intercept方法参数详解 Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) Object obj：与JDK代理相同，都是代理对象的引用，一般不使用 Method method：代理类调用接口中方法时触发的方法对象 Object[] args：代理类调用接口中方法时传递的参数列表 MethodProxy proxy：方法的代理对象引用，一般不使用 //定义一个类 public class Customer { public String buy(String name){ return name+&quot;购买成功&quot;; } } //实现基于CGLIB的动态代理 public class CGLIBProxyDemo { public static void main(String[] args) { //创建被代理对象 final Customer customer = new Customer(); Customer proxyCustomer = (Customer)Enhancer.create(customer.getClass(), new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object result = null; if(&quot;buy&quot;.equalsIgnoreCase(method.getName())){ result = method.invoke(customer,args)+&quot;,我被加强了&quot;; }else{ result = method.invoke(customer); } return result; } }); String content = proxyCustomer.buy(&quot;香蕉&quot;); System.out.println(content); } } 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/Proxy/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/Proxy/"},{"title":"Java原生实现HTTP的GET和POST请求","content":"一、原生实现GET请求 public static String Get(String url) { HttpURLConnection conn = null; InputStream is = null; BufferedReader br = null; String line = null; StringBuilder sb = new StringBuilder(); try { //获取URL连接对象强转为HTTPURL连接对象 conn = (HttpURLConnection) new URL(url).openConnection(); //设置HTTP请求方式为GET conn.setRequestMethod(&quot;GET&quot;); //设置连接超时时间为3秒 conn.setConnectTimeout(3000); //设置读取时间超时为5秒 conn.setReadTimeout(5000); // 设置请求头 conn.setRequestProperty(&quot;Content-Type&quot;, &quot;text/html;charset=utf-8&quot;); conn.setRequestProperty(&quot;Accept&quot;, &quot;text/html,application/xhtml+xml,application/xml,application/json;&quot;); conn.setRequestProperty(&quot;User-Agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36&quot;); //发送请求 conn.connect(); //获取响应码，判断请求是否成功 if (conn.getResponseCode() == 200) { is = conn.getInputStream(); br = new BufferedReader(new InputStreamReader(is, &quot;UTF-8&quot;)); while ((line = br.readLine()) != null) { // 当line不为null时，将line追加到sb中 sb.append(line + &quot;\\n&quot;); } } else { // 返回错误信息 return &quot;ResponseCode is Error:&quot; + conn.getResponseCode(); } } catch (IOException e) { e.printStackTrace(); } finally { // 释放IO流 if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } // 返回sb的String内容 return sb.toString(); } 问：怎么没有GET传参的方法？ 答：一般我们会直接通过QueryString将多个键值的参数用&amp;连接再与URL直接拼接，所以实际上我们还是只做了请求定向的链接，所以不需要传参的方法 二、原生实现POST请求 public static String Post(String url, String requestContent) { System.out.println(&quot;请求的URL：&quot;+url); System.out.println(&quot;请求的参数：&quot;+requestContent); HttpURLConnection conn = null; OutputStream os = null; BufferedWriter bw = null; InputStream is = null; String line = null; StringBuilder sb = new StringBuilder(); BufferedReader br = null; try { // 获取URL连接对象强转为HTTPURL连接对象 conn = (HttpURLConnection) new URL(url).openConnection(); //设置请求头 conn.setRequestProperty(&quot;Connection&quot;,&quot;keep-alive&quot;); conn.setRequestMethod(&quot;POST&quot;); //因为POST请求需要对流进行读写，所以需要设置输入输出均为true conn.setDoInput(true); conn.setDoOutput(true); // 设置超时时间 conn.setConnectTimeout(3000); conn.setReadTimeout(15000); // 设置请求头 conn.setRequestProperty(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded;charset=utf-8&quot;); conn.setRequestProperty(&quot;Accept&quot;, &quot;text/html,application/xhtml+xml,application/xml,application/json,application/x-www-form-urlencoded&quot;); conn.setRequestProperty(&quot;User-Agent&quot;, &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36&quot;); // 将POST参数转为字节数组写到流中并刷新流（这一步相当于提交参数） conn.getOutputStream().write(requestContent.getBytes(&quot;UTF-8&quot;)); conn.getOutputStream().flush(); // 获取响应码，判断请求是否成功 if (conn.getResponseCode()==200){ is = conn.getInputStream(); br = new BufferedReader(new InputStreamReader(is, &quot;UTF-8&quot;)); while((line=br.readLine())!=null){ sb.append(line+&quot;\\n&quot;); } }else{ // 返回错误信息 return &quot;ResponseCode is Error:&quot; + conn.getResponseCode(); } } catch (IOException e) { e.printStackTrace(); }finally { // 释放IO流 if(br!=null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(bw!=null){ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } // 返回sb的String内容 return sb.toString(); } 问：为什么我的不成功？ 答：请检查代码是否与我的完全一致，还有POST请求头中的Content-Type有几种固定的格式，并非你想写什么就可以写什么，就比如，如果我设置Content-Type为text/html;charset=utf-8，在GET请求下是正常的，在POST请求中是不行的，无法正确提交你的参数，因为你的参数并非是html格式的 三、POST请求中的常见Content-Type 1. 以QueryString的形式去提交POST参数 你需要使用的Content-Type为application/x-www-form-urlencoded conn.setRequestProperty(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded;charset=utf-8&quot;); 2. 以FormData的形式去提交POST参数 你需要使用的Content-Type为multipart/form-data conn.setRequestProperty(&quot;Content-Type&quot;, &quot;multipart/form-data&quot;); 3. 以Json格式去提交POST参数 你需要使用的Content-Type为application/json conn.setRequestProperty(&quot;Content-Type&quot;, &quot;application/json;charset=utf-8&quot;); 问：啥叫QueryString,FormData,Json？ 答：请百度 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/JavaHttp/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/JavaHttp/"},{"title":"Win10系统盘空间优化总结","content":"一、找不到这几个系统文件怎么办？ 那是因为这几个都是系统文件，不仅被隐藏还受系统保护，所以要取消隐藏系统保护文件，看下面一步步来就好 1. 第一步 2. 第二步 这样就可以显示被隐藏的系统文件了，但我们尽量不要随便删除系统文件，否则会产生一些无法预料的错误 二、删除系统休眠文件 hiberfil.sys 我们常说的电脑睡眠，是指电脑进入低功耗状态，将它的工作状态保存进内存，当我们恢复操作时会自动加载之前的状态，而休眠功能则是将它的工作状态保存进磁盘，这就形成了我们现在的这个hiberfil.sys文件，事实上我们大多数人都不会用到这个功能，毕竟我们连这个文件听都没听过，所以这个文件是可以放心删除的，但是请按照我下面的操作进行删除，否则出现其他的问题，可没办法解决 以下内容摘自百度百科：https://baike.baidu.com/item/hiberfil.sys/4299821?fr=aladdin 在一些较新的具有STR功能的主板上安装了xp及更新的操作系统后，在系统区根目录下会有一个 hiberfil.sys 的文件，它的大小和内存容量一样，这是什么文件呢？原来这个就是系统的休眠功能所占用的硬盘空间的文件（Windows 默认休眠功能是处于打开状态的），电脑处于休眠状态时，内存中的所有内容将保存到硬盘，并且电脑会被关闭。电脑在返回打开状态之后，关闭电脑时打开的所有程序和文档将全都还原到桌面。也就是说，当你让电脑进入休眠状态时，Windows 在关闭系统前将所有的内存内容写入hiberfil.sys文件。而后，当你重新打开电脑，操作系统使用Hiberfil.sys把所有信息放回内存，电脑恢复到关闭前的状态。然而，Windows 在硬盘上留下了hiberfil.sys文件，即使这个文件不再需要了。 1. 第一步：管理员权限运行CMD 2. 第二步：输入命令powercfg -h off，回车执行 powercfg -h off 片刻之后，我们发现C盘中的hiberfil.sys已经被删除，C盘空间也多了出来，删除成功了 要是之后还想恢复这个休眠功能，只需要再次以管理员权限运行CMD，输入命令 powercfg -h on 即可恢复 二、移动虚拟内存文件 pagefile.sys 什么是pagefile.sys？ 以下内容摘自百度百科：https://baike.baidu.com/item/pagefile.sys/460068?fr=aladdin](https://baike.baidu.com/item/pagefile.sys/460068?fr=aladdin) pagefile.sys即安装过程中创建的虚拟内存页面文件。这个文件的大小是自己系统虚拟内存设置的最小值的大小。假如虚拟内存的设置为800MB－1600MB 那么你在自己的设定驱动盘下面就可看到大小为800MB的pagefile.sys文件 也就是说pagefile.sys文件是系统管理虚拟内存的文件，所以说我们是不建议删除它的，一般我们会将这个文件移动到其他空闲的盘符上，以节省C盘空间 第一步：右键“此电脑”，点击“属性”，在弹出窗口点击“高级系统设置”，如下 第二步：在弹出窗口选择“高级”选项卡，点击“性能”下的“设置” 第三步：在弹出窗口选择“高级”选项卡，点击“更改” 第四步：我是已经移动过这个文件，所以C盘这里显示空，没移动前会显示“托管的系统” 第五步：选择一个你的空闲磁盘，准备将虚拟内存文件移动过去的磁盘，选择它，然后点击“系统管理的大小”，然后点击“设置”，最后“确定”，可能会让你重启系统，如果提示，请忽略，我们还要继续操作 第六步：这次选择C盘，然后选择“无分页文件”，点击“设置”，最后点击“确认”，然后一路确认，直到所有窗口关闭，点击“立即重新启动”系统，等待系统重启就好了，系统重启后，我们发现C盘中的pagefile.sys不在了，打开我们选择的空闲硬盘，可以在根目录看到被移动的pagefile.sys文件 这两次下来，C盘应该能被释放不少空间，Win10的小技巧分享结束 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/LwW9qXRhj/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/LwW9qXRhj/"},{"title":"常用注册表自定义功能","content":"一、右键创建文本文件 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\\.txt] @=&quot;txtfile&quot; &quot;Content Type&quot;=&quot;text/plain&quot; [HKEY_CLASSES_ROOT\\.txt\\ShellNew] &quot;NullFile&quot;=&quot;&quot; [HKEY_CLASSES_ROOT\\txtfile] @=&quot;文本文档&quot; [HKEY_CLASSES_ROOT\\txtfile\\shell] [HKEY_CLASSES_ROOT\\txtfile\\shell\\open] [HKEY_CLASSES_ROOT\\txtfile\\shell\\open\\command] @=&quot;NOTEPAD.EXE %1&quot; 二、右键记事本打开 Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\*\\shell\\notepad] @=&quot;用记事本打开 &quot; [HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\*\\shell\\notepad\\command] @=&quot;notepad %1&quot; 三、右键查看文件Hash值 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\\*\\shell\\hash] &quot;MUIVerb&quot;=&quot;Hash&quot; &quot;SubCommands&quot;=&quot;&quot; ; SHA1 [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\01menu] &quot;MUIVerb&quot;=&quot;SHA1&quot; [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\01menu\\command] @=&quot;powershell -noexit get-filehash -literalpath '%1' -algorithm SHA1 | format-list&quot; ; SHA256 [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\02menu] &quot;MUIVerb&quot;=&quot;SHA256&quot; [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\02menu\\command] @=&quot;powershell -noexit get-filehash -literalpath '%1' -algorithm SHA256 | format-list&quot; ; SHA384 [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\03menu] &quot;MUIVerb&quot;=&quot;SHA384&quot; [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\03menu\\command] @=&quot;powershell -noexit get-filehash -literalpath '%1' -algorithm SHA384 | format-list&quot; ; SHA512 [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\04menu] &quot;MUIVerb&quot;=&quot;SHA512&quot; [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\04menu\\command] @=&quot;powershell -noexit get-filehash -literalpath '%1' -algorithm SHA512 | format-list&quot; ; MACTripleDES [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\05menu] &quot;MUIVerb&quot;=&quot;MACTripleDES&quot; [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\05menu\\command] @=&quot;powershell -noexit get-filehash -literalpath '%1' -algorithm MACTripleDES | format-list&quot; ; MD5 [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\06menu] &quot;MUIVerb&quot;=&quot;MD5&quot; [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\06menu\\command] @=&quot;powershell -noexit get-filehash -literalpath '%1' -algorithm MD5 | format-list&quot; ; RIPEMD160 [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\07menu] &quot;MUIVerb&quot;=&quot;RIPEMD160&quot; [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\07menu\\command] @=&quot;powershell -noexit get-filehash -literalpath '%1' -algorithm RIPEMD160 | format-list&quot; ; Allget-filehash -literalpath '%1' -algorithm RIPEMD160 | format-list [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\08menu] &quot;CommandFlags&quot;=dword:00000020 &quot;MUIVerb&quot;=&quot;显示全部&quot; [HKEY_CLASSES_ROOT\\*\\shell\\hash\\shell\\08menu\\command] @=&quot;powershell -noexit get-filehash -literalpath '%1' -algorithm SHA1 | format-list;get-filehash -literalpath '%1' -algorithm SHA256 | format-list;get-filehash -literalpath '%1' -algorithm SHA384 | format-list;get-filehash -literalpath '%1' -algorithm SHA512 | format-list;get-filehash -literalpath '%1' -algorithm MACTripleDES | format-list;get-filehash -literalpath '%1' -algorithm MD5 | format-list;get-filehash -literalpath '%1' -algorithm RIPEMD160 | format-list&quot; 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/hab_ZN2hO/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/hab_ZN2hO/"},{"title":"自定义注解简单模拟Junit实现测试","content":"一、创建自定义注解 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyTest {} 二、创建测试用的类和方法 public class Demo { @MyTest public void show(){ System.out.println(&quot;show()方法&quot;); } } 三、实现自定义注解功能 public class MyTest { public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException { Class&lt;Demo&gt; dc = Demo.class; Demo demo = dc.newInstance(); Method[] methods = dc.getDeclaredMethods(); for (Method method : methods) { if(method.isAnnotationPresent(com.zhiyuan.annotation.MyTest.class)){ method.invoke(demo); } } } } 四、执行结果 //show()方法 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/SHhX20vPx/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/SHhX20vPx/"},{"title":"SemanticUI学习（11）段落","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src = &quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、普通段落 &lt;div class=&quot;ui segment&quot;&gt; &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 三、占位符段落 &lt;div class=&quot;ui placeholder segment&quot;&gt; &lt;div class=&quot;ui icon header&quot;&gt; &lt;i class=&quot;pdf file outline icon&quot;&gt;&lt;/i&gt;No documents are listened for this customer &lt;/div&gt; &lt;div class=&quot;ui primary button&quot;&gt;Add Document&lt;/div&gt; &lt;/div&gt; 四、阴影段落 &lt;div class=&quot;ui raised segment&quot;&gt; &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 五、堆叠段落 &lt;div class=&quot;ui stacked segment&quot;&gt; &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui tall stacked segment&quot;&gt; &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui piled segment&quot;&gt; &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 六、多彩段落 &lt;div class=&quot;ui vertical raised segments&quot;&gt; &lt;div class=&quot;ui orange segment&quot;&gt; &lt;p&gt;top&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui red segment&quot;&gt; &lt;p&gt;center&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui green segment&quot;&gt; &lt;p&gt;center&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui violet segment&quot;&gt; &lt;p&gt;center&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui blue segment&quot;&gt; &lt;p&gt;bottom&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui horizontal raised segments&quot;&gt; &lt;div class=&quot;ui orange segment&quot;&gt; &lt;p&gt;top&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui red segment&quot;&gt; &lt;p&gt;center&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui green segment&quot;&gt; &lt;p&gt;center&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui violet segment&quot;&gt; &lt;p&gt;center&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui blue segment&quot;&gt; &lt;p&gt;bottom&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; 七、带加载器段落 &lt;div class=&quot;ui loading segment&quot;&gt; &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 八、反转段落 &lt;div class=&quot;ui inverted segment&quot;&gt; &lt;div class=&quot;ui inverted placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 九、垂直附加段落 &lt;div class=&quot;ui top attached segment&quot;&gt; &lt;div class=&quot;ui header&quot;&gt;top&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui inverted attached segment&quot;&gt; &lt;div class=&quot;ui inverted placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui bottom attached segment&quot;&gt; &lt;div class=&quot;ui header&quot;&gt;bottom&lt;/div&gt; &lt;/div&gt; 十、分级内容段落 &lt;div class=&quot;ui secondary inverted green segment&quot;&gt; &lt;p&gt;二级分级颜色展示&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui tertiary inverted green segment&quot;&gt; &lt;p&gt;三级分级颜色展示&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui inverted green segment&quot;&gt; &lt;p&gt;默认分级颜色展示&lt;/p&gt; &lt;/div&gt; 十一、圆形段落 &lt;div class=&quot;ui red inverted circular segment&quot;&gt; &lt;div class=&quot;ui inverted header&quot;&gt; 现在购买&lt;div class=&quot;sub header&quot;&gt;只要$998&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 十二、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/06BP2pOk4/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/06BP2pOk4/"},{"title":"SemanticUI学习（10）揭示效果","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、Fade（渐隐渐现效果） &lt;div class=&quot;ui fade reveal&quot;&gt; &lt;div class=&quot;visible content&quot;&gt; &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot; class=&quot;ui small image&quot;&gt; &lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt; &lt;img src=&quot;./img/ade.jpg&quot; alt=&quot;&quot; class=&quot;ui small image&quot;&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui fade reveal small image&quot;&gt; &lt;div class=&quot;visible content&quot;&gt; &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt; &lt;img src=&quot;./img/ade.jpg&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;/div&gt; 三、Move（左右移动效果） &lt;div class=&quot;ui move left reveal small image&quot;&gt; &lt;div class=&quot;visible content&quot;&gt; &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt; &lt;img src=&quot;./img/ade.jpg&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui move right reveal small image&quot;&gt; &lt;div class=&quot;visible content&quot;&gt; &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt; &lt;img src=&quot;./img/ade.jpg&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui move up reveal small image&quot;&gt; &lt;div class=&quot;visible content&quot;&gt; &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt; &lt;img src=&quot;./img/ade.jpg&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui move down reveal small image&quot;&gt; &lt;div class=&quot;visible content&quot;&gt; &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt; &lt;img src=&quot;./img/ade.jpg&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;/div&gt; 四、Rotate（旋转揭示效果） &lt;div class=&quot;ui small circular rotate right reveal image&quot;&gt; &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot; class=&quot;visible content&quot;&gt; &lt;img src=&quot;./img/ade.jpg&quot; alt=&quot;&quot; class=&quot;hidden content&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui small circular rotate left reveal image&quot;&gt; &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot; class=&quot;visible content&quot;&gt; &lt;img src=&quot;./img/ade.jpg&quot; alt=&quot;&quot; class=&quot;hidden content&quot;&gt; &lt;/div&gt; 五、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/6TaBTojkN/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/6TaBTojkN/"},{"title":"SemanticUI学习（9）占位符","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、普通占位符 &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 三、segment占位符 &lt;div class=&quot;ui raised segment&quot;&gt; &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 四、不同长度占位符 &lt;div class=&quot;ui raised segment&quot;&gt; &lt;div class=&quot;ui placeholder&quot;&gt; &lt;div class=&quot;very short line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;short line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;medium line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;long line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;very long line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;full line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 五、适应容器占位符 &lt;div class=&quot;ui raised segment&quot;&gt; &lt;div class=&quot;ui fluid placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;very long line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;full line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;medium line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;short line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;long line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 六、反转占位符 &lt;div class=&quot;ui raised inverted segment&quot;&gt; &lt;div class=&quot;ui inverted placeholder&quot;&gt; &lt;div class=&quot;image header&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;paragraph&quot;&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;div class=&quot;line&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 七、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/9njMczwwO/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/9njMczwwO/"},{"title":"SemanticUI学习（8）加载器","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、普通加载器 &lt;div class=&quot;ui block header&quot;&gt;普通加载器样式&lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui loader&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 三、加载器反转 &lt;div class=&quot;ui block header&quot;&gt;加载器反转效果&lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active inverted dimmer&quot;&gt; &lt;div class=&quot;ui loader&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 四、带文字描述的加载器 &lt;div class=&quot;ui block header&quot;&gt;带文字的加载器&lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 五、不同状态的加载器 &lt;div class=&quot;ui block header&quot;&gt;不同状态的加载器&lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui indeterminate text loader&quot;&gt;正在准备文件&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 六、加载器的显示与隐藏 &lt;div class=&quot;ui block header&quot;&gt;加载器的显示与隐藏&lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active loader&quot;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui disabled loader&quot;&gt;&lt;/div&gt; &lt;/div&gt; 七、内联样式加载器 &lt;div class=&quot;ui block header&quot;&gt;内联样式加载器&lt;/div&gt; &lt;div class=&quot;ui active text inline loader&quot;&gt;加载中&lt;/div&gt; 八、不同尺寸加载器 &lt;div class=&quot;ui block header&quot;&gt;不同尺寸加载器&lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui mini text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui tiny text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui small text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui medium text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui large text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui big text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui huge text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;div class=&quot;ui active dimmer&quot;&gt; &lt;div class=&quot;ui massive text loader&quot;&gt;加载中&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 九、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/QDJU1dBj2/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/QDJU1dBj2/"},{"title":"Java中所有TimeZoneID","content":"一、使用代码输出Java中所有TimeZoneID public static void main(String[] args) { Set&lt;String&gt; zoneIds = ZoneId.getAvailableZoneIds(); for (String zoneId : zoneIds) { System.out.println(zoneId); } } 二、所有TimeZoneID表格 时区ID Asia/Aden America/Cuiaba Etc/GMT+9 Etc/GMT+8 Africa/Nairobi America/Marigot Asia/Aqtau Pacific/Kwajalein America/El_Salvador Asia/Pontianak Africa/Cairo Pacific/Pago_Pago Africa/Mbabane Asia/Kuching Pacific/Honolulu Pacific/Rarotonga America/Guatemala Australia/Hobart Europe/London America/Belize America/Panama Asia/Chungking America/Managua America/Indiana/Petersburg Asia/Yerevan Europe/Brussels GMT Europe/Warsaw America/Chicago Asia/Kashgar Chile/Continental Pacific/Yap CET Etc/GMT-1 Etc/GMT-0 Europe/Jersey America/Tegucigalpa Etc/GMT-5 Europe/Istanbul America/Eirunepe Etc/GMT-4 America/Miquelon Etc/GMT-3 Europe/Luxembourg Etc/GMT-2 Etc/GMT-9 America/Argentina/Catamarca Etc/GMT-8 Etc/GMT-7 Etc/GMT-6 Europe/Zaporozhye Canada/Yukon Canada/Atlantic Atlantic/St_Helena Australia/Tasmania Libya Europe/Guernsey America/Grand_Turk US/Pacific-New Asia/Samarkand America/Argentina/Cordoba Asia/Phnom_Penh Africa/Kigali Asia/Almaty US/Alaska Asia/Dubai Europe/Isle_of_Man America/Araguaina Cuba Asia/Novosibirsk America/Argentina/Salta Etc/GMT+3 Africa/Tunis Etc/GMT+2 Etc/GMT+1 Pacific/Fakaofo Africa/Tripoli Etc/GMT+0 Israel Africa/Banjul Etc/GMT+7 Indian/Comoro Etc/GMT+6 Etc/GMT+5 Etc/GMT+4 Pacific/Port_Moresby US/Arizona Antarctica/Syowa Indian/Reunion Pacific/Palau Europe/Kaliningrad America/Montevideo Africa/Windhoek Asia/Karachi Africa/Mogadishu Australia/Perth Brazil/East Etc/GMT Asia/Chita Pacific/Easter Antarctica/Davis Antarctica/McMurdo Asia/Macao America/Manaus Africa/Freetown Europe/Bucharest Asia/Tomsk America/Argentina/Mendoza Asia/Macau Europe/Malta Mexico/BajaSur Pacific/Tahiti Africa/Asmera Europe/Busingen America/Argentina/Rio_Gallegos Africa/Malabo Europe/Skopje America/Catamarca America/Godthab Europe/Sarajevo Australia/ACT GB-Eire Africa/Lagos America/Cordoba Europe/Rome Asia/Dacca Indian/Mauritius Pacific/Samoa America/Regina America/Fort_Wayne America/Dawson_Creek Africa/Algiers Europe/Mariehamn America/St_Johns America/St_Thomas Europe/Zurich America/Anguilla Asia/Dili America/Denver Africa/Bamako Europe/Saratov GB Mexico/General Pacific/Wallis Europe/Gibraltar Africa/Conakry Africa/Lubumbashi Asia/Istanbul America/Havana NZ-CHAT Asia/Choibalsan America/Porto_Acre Asia/Omsk Europe/Vaduz US/Michigan Asia/Dhaka America/Barbados Europe/Tiraspol Atlantic/Cape_Verde Asia/Yekaterinburg America/Louisville Pacific/Johnston Pacific/Chatham Europe/Ljubljana America/Sao_Paulo Asia/Jayapura America/Curacao Asia/Dushanbe America/Guyana America/Guayaquil America/Martinique Portugal Europe/Berlin Europe/Moscow Europe/Chisinau America/Puerto_Rico America/Rankin_Inlet Pacific/Ponape Europe/Stockholm Europe/Budapest America/Argentina/Jujuy Australia/Eucla Asia/Shanghai Universal Europe/Zagreb America/Port_of_Spain Europe/Helsinki Asia/Beirut Asia/Tel_Aviv Pacific/Bougainville US/Central Africa/Sao_Tome Indian/Chagos America/Cayenne Asia/Yakutsk Pacific/Galapagos Australia/North Europe/Paris Africa/Ndjamena Pacific/Fiji America/Rainy_River Indian/Maldives Australia/Yancowinna SystemV/AST4 Asia/Oral America/Yellowknife Pacific/Enderbury America/Juneau Australia/Victoria America/Indiana/Vevay Asia/Tashkent Asia/Jakarta Africa/Ceuta Asia/Barnaul America/Recife America/Buenos_Aires America/Noronha America/Swift_Current Australia/Adelaide America/Metlakatla Africa/Djibouti America/Paramaribo Asia/Qostanay Europe/Simferopol Europe/Sofia Africa/Nouakchott Europe/Prague America/Indiana/Vincennes Antarctica/Mawson America/Kralendijk Antarctica/Troll Europe/Samara Indian/Christmas America/Antigua Pacific/Gambier America/Indianapolis America/Inuvik America/Iqaluit Pacific/Funafuti UTC Antarctica/Macquarie Canada/Pacific America/Moncton Africa/Gaborone Pacific/Chuuk Asia/Pyongyang America/St_Vincent Asia/Gaza Etc/Universal PST8PDT Atlantic/Faeroe Asia/Qyzylorda Canada/Newfoundland America/Kentucky/Louisville America/Yakutat Asia/Ho_Chi_Minh Antarctica/Casey Europe/Copenhagen Africa/Asmara Atlantic/Azores Europe/Vienna ROK Pacific/Pitcairn America/Mazatlan Australia/Queensland Pacific/Nauru Europe/Tirane Asia/Kolkata SystemV/MST7 Australia/Canberra MET Australia/Broken_Hill Europe/Riga America/Dominica Africa/Abidjan America/Mendoza America/Santarem Kwajalein America/Asuncion Asia/Ulan_Bator NZ America/Boise Australia/Currie EST5EDT Pacific/Guam Pacific/Wake Atlantic/Bermuda America/Costa_Rica America/Dawson Asia/Chongqing Eire Europe/Amsterdam America/Indiana/Knox America/North_Dakota/Beulah Africa/Accra Atlantic/Faroe Mexico/BajaNorte America/Maceio Etc/UCT Pacific/Apia GMT0 America/Atka Pacific/Niue Australia/Lord_Howe Europe/Dublin Pacific/Truk MST7MDT America/Monterrey America/Nassau America/Jamaica Asia/Bishkek America/Atikokan Atlantic/Stanley Australia/NSW US/Hawaii SystemV/CST6 Indian/Mahe Asia/Aqtobe America/Sitka Asia/Vladivostok Africa/Libreville Africa/Maputo Zulu America/Kentucky/Monticello Africa/El_Aaiun Africa/Ouagadougou America/Coral_Harbour Pacific/Marquesas Brazil/West America/Aruba America/North_Dakota/Center America/Cayman Asia/Ulaanbaatar Asia/Baghdad Europe/San_Marino America/Indiana/Tell_City America/Tijuana Pacific/Saipan SystemV/YST9 Africa/Douala America/Chihuahua America/Ojinaga Asia/Hovd America/Anchorage Chile/EasterIsland America/Halifax Antarctica/Rothera America/Indiana/Indianapolis US/Mountain Asia/Damascus America/Argentina/San_Luis America/Santiago Asia/Baku America/Argentina/Ushuaia Atlantic/Reykjavik Africa/Brazzaville Africa/Porto-Novo America/La_Paz Antarctica/DumontDUrville Asia/Taipei Antarctica/South_Pole Asia/Manila Asia/Bangkok Africa/Dar_es_Salaam Poland Atlantic/Madeira Antarctica/Palmer America/Thunder_Bay Africa/Addis_Ababa Asia/Yangon Europe/Uzhgorod Brazil/DeNoronha Asia/Ashkhabad Etc/Zulu America/Indiana/Marengo America/Creston America/Punta_Arenas America/Mexico_City Antarctica/Vostok Asia/Jerusalem Europe/Andorra US/Samoa PRC Asia/Vientiane Pacific/Kiritimati America/Matamoros America/Blanc-Sablon Asia/Riyadh Iceland Pacific/Pohnpei Asia/Ujung_Pandang Atlantic/South_Georgia Europe/Lisbon Asia/Harbin Europe/Oslo Asia/Novokuznetsk CST6CDT Atlantic/Canary America/Knox_IN Asia/Kuwait SystemV/HST10 Pacific/Efate Africa/Lome America/Bogota America/Menominee America/Adak Pacific/Norfolk Europe/Kirov America/Resolute Pacific/Tarawa Africa/Kampala Asia/Krasnoyarsk Greenwich SystemV/EST5 America/Edmonton Europe/Podgorica Australia/South Canada/Central Africa/Bujumbura America/Santo_Domingo US/Eastern Europe/Minsk Pacific/Auckland Africa/Casablanca America/Glace_Bay Canada/Eastern Asia/Qatar Europe/Kiev Singapore Asia/Magadan SystemV/PST8 America/Port-au-Prince Europe/Belfast America/St_Barthelemy Asia/Ashgabat Africa/Luanda America/Nipigon Atlantic/Jan_Mayen Brazil/Acre Asia/Muscat Asia/Bahrain Europe/Vilnius America/Fortaleza Etc/GMT0 US/East-Indiana America/Hermosillo America/Cancun Africa/Maseru Pacific/Kosrae Africa/Kinshasa Asia/Kathmandu Asia/Seoul Australia/Sydney America/Lima Australia/LHI America/St_Lucia Europe/Madrid America/Bahia_Banderas America/Montserrat Asia/Brunei America/Santa_Isabel Canada/Mountain America/Cambridge_Bay Asia/Colombo Australia/West Indian/Antananarivo Australia/Brisbane Indian/Mayotte US/Indiana-Starke Asia/Urumqi US/Aleutian Europe/Volgograd America/Lower_Princes America/Vancouver Africa/Blantyre America/Rio_Branco America/Danmarkshavn America/Detroit America/Thule Africa/Lusaka Asia/Hong_Kong Iran America/Argentina/La_Rioja Africa/Dakar SystemV/CST6CDT America/Tortola America/Porto_Velho Asia/Sakhalin Etc/GMT+10 America/Scoresbysund Asia/Kamchatka Asia/Thimbu Africa/Harare Etc/GMT+12 Etc/GMT+11 Navajo America/Nome Europe/Tallinn Turkey Africa/Khartoum Africa/Johannesburg Africa/Bangui Europe/Belgrade Jamaica Africa/Bissau Asia/Tehran WET Europe/Astrakhan Africa/Juba America/Campo_Grande America/Belem Etc/Greenwich Asia/Saigon America/Ensenada Pacific/Midway America/Jujuy Africa/Timbuktu America/Bahia America/Goose_Bay America/Virgin America/Pangnirtung Asia/Katmandu America/Phoenix Africa/Niamey America/Whitehorse Pacific/Noumea Asia/Tbilisi America/Montreal Asia/Makassar America/Argentina/San_Juan Hongkong UCT Asia/Nicosia America/Indiana/Winamac SystemV/MST7MDT America/Argentina/ComodRivadavia America/Boa_Vista America/Grenada Asia/Atyrau Australia/Darwin Asia/Khandyga Asia/Kuala_Lumpur Asia/Famagusta Asia/Thimphu Asia/Rangoon Europe/Bratislava Asia/Calcutta America/Argentina/Tucuman Asia/Kabul Indian/Cocos Japan Pacific/Tongatapu America/New_York Etc/GMT-12 Etc/GMT-11 America/Nuuk Etc/GMT-10 SystemV/YST9YDT Europe/Ulyanovsk Etc/GMT-14 Etc/GMT-13 W-SU America/Merida EET America/Rosario Canada/Saskatchewan America/St_Kitts Arctic/Longyearbyen America/Fort_Nelson America/Caracas America/Guadeloupe Asia/Hebron Indian/Kerguelen SystemV/PST8PDT Africa/Monrovia Asia/Ust-Nera Egypt Asia/Srednekolymsk America/North_Dakota/New_Salem Asia/Anadyr Australia/Melbourne Asia/Irkutsk America/Shiprock America/Winnipeg Europe/Vatican Asia/Amman Etc/UTC SystemV/AST4ADT Asia/Tokyo America/Toronto Asia/Singapore Australia/Lindeman America/Los_Angeles SystemV/EST5EDT Pacific/Majuro America/Argentina/Buenos_Aires Europe/Nicosia Pacific/Guadalcanal Europe/Athens US/Pacific Europe/Monaco 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/QwkVexFfD/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/QwkVexFfD/"},{"title":"SemanticUI学习（7）列表","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、普通列表 &lt;div class=&quot;ui list&quot;&gt; &lt;div class=&quot;item&quot;&gt;list&lt;/div&gt; &lt;div class=&quot;item&quot;&gt;list&lt;/div&gt; &lt;div class=&quot;item&quot;&gt;list&lt;/div&gt; &lt;div class=&quot;item&quot;&gt;list&lt;/div&gt; &lt;/div&gt; 三、图标列表 &lt;div class=&quot;ui list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;users icon&quot;&gt;&lt;/i&gt; SemanticUI &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;marker icon&quot;&gt;&lt;/i&gt; China &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;mail icon&quot;&gt;&lt;/i&gt; zhiyuandnc666@gmail.com &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;linkify icon&quot;&gt;&lt;/i&gt; www.semeantic.com &lt;/div&gt; &lt;/div&gt; 四、好友列表样式 &lt;div class=&quot;ui relaxed divided list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;big github middle aligned icon&quot;&gt;&lt;/i&gt; &lt;div class=&quot;content&quot;&gt; &lt;a href=&quot;#&quot; class=&quot;header&quot;&gt;Semantic-UI&lt;/a&gt; &lt;div class=&quot;description&quot;&gt;Update 10 mins ago&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;big github middle aligned icon&quot;&gt;&lt;/i&gt; &lt;div class=&quot;content&quot;&gt; &lt;a href=&quot;#&quot; class=&quot;header&quot;&gt;Bootstrap&lt;/a&gt; &lt;div class=&quot;description&quot;&gt;Update 22 days ago&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;big github middle aligned icon&quot;&gt;&lt;/i&gt; &lt;div class=&quot;content&quot;&gt; &lt;a href=&quot;#&quot; class=&quot;header&quot;&gt;Element-UI&lt;/a&gt; &lt;div class=&quot;description&quot;&gt;Update 22 hours ago&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;big github middle aligned icon&quot;&gt;&lt;/i&gt; &lt;div class=&quot;content&quot;&gt; &lt;a href=&quot;#&quot; class=&quot;header&quot;&gt;Vue&lt;/a&gt; &lt;div class=&quot;description&quot;&gt;Update 22 days ago&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;i class=&quot;big github middle aligned icon&quot;&gt;&lt;/i&gt; &lt;div class=&quot;content&quot;&gt; &lt;a href=&quot;#&quot; class=&quot;header&quot;&gt;NodeJS&lt;/a&gt; &lt;div class=&quot;description&quot;&gt;Update 22 days ago&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 五、水平列表 &lt;div class=&quot;ui horizontal list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;ZhiYuanCaiJi&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;ZhiYuanCaiJi&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;ZhiYuanCaiJi&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;ZhiYuanCaiJi&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;ZhiYuanCaiJi&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 六、垂直列表 &lt;div class=&quot;ui vertical list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;ui content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;ui content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;ui content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;ui content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;ui content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 七、动画效果列表 &lt;div class=&quot;ui middle aligned animated list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 八、垂直松散列表 &lt;div class=&quot;ui relaxed divided list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 九、水平松散列表 &lt;div class=&quot;ui horizontal relaxed list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 十、垂直单元格形式列表 &lt;div class=&quot;ui celled vertical list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 十一、水平单元格形式列表 &lt;div class=&quot;ui horizontal celled list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 十二、不同尺寸的列表 &lt;div class=&quot;ui horizontal mini list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui divider&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui horizontal tiny list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui divider&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui horizontal medium list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui divider&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui horizontal large list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui divider&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui horizontal big list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui divider&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui horizontal huge list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui divider&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui horizontal massive list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;./img/daniel.jpg&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;zhiyuan&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 十三、好友列表 &lt;div class=&quot;ui vertical relaxed middle aligned divided list&quot;&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&amp;size=100&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;无他，唯手熟尔&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;right floated content&quot;&gt; &lt;div class=&quot;ui orange basic button&quot;&gt; &lt;i class=&quot;user plus icon&quot;&gt;&lt;/i&gt;添加好友 &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&amp;size=100&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;无他，唯手熟尔&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;right floated content&quot;&gt; &lt;div class=&quot;ui orange basic button&quot;&gt; &lt;i class=&quot;user plus icon&quot;&gt;&lt;/i&gt;添加好友 &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&amp;size=100&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;无他，唯手熟尔&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;right floated content&quot;&gt; &lt;div class=&quot;ui orange basic button&quot;&gt; &lt;i class=&quot;user plus icon&quot;&gt;&lt;/i&gt;添加好友 &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&amp;size=100&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;无他，唯手熟尔&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;right floated content&quot;&gt; &lt;div class=&quot;ui orange basic button&quot;&gt; &lt;i class=&quot;user plus icon&quot;&gt;&lt;/i&gt;添加好友 &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;item&quot;&gt; &lt;img src=&quot;https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&amp;size=100&quot; alt=&quot;&quot; class=&quot;ui avatar image&quot;&gt; &lt;div class=&quot;content&quot;&gt; &lt;div class=&quot;header&quot;&gt;絷缘&lt;/div&gt; &lt;div class=&quot;description&quot;&gt;无他，唯手熟尔&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;right floated content&quot;&gt; &lt;div class=&quot;ui orange basic button&quot;&gt; &lt;i class=&quot;user plus icon&quot;&gt;&lt;/i&gt;添加好友 &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 十四、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/eycw6BwYh/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/eycw6BwYh/"},{"title":"SemanticUI学习（6）标签","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、普通标签 &lt;div class=&quot;ui label&quot;&gt; &lt;i class=&quot;mail red icon&quot;&gt;&lt;/i&gt;999+ &lt;/div&gt; 三、带头像的标签 &lt;div class=&quot;ui image label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt;zhiyuan &lt;/div&gt; &lt;div class=&quot;ui image blue label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt;zhiyuan &lt;/div&gt; &lt;div class=&quot;ui image green label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt;zhiyuan &lt;/div&gt; &lt;div class=&quot;ui image violet label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt;zhiyuan &lt;/div&gt; &lt;div class=&quot;ui image yellow label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt;zhiyuan &lt;/div&gt; &lt;div class=&quot;ui image orange label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt;zhiyuan &lt;/div&gt; &lt;div class=&quot;ui image red label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt;zhiyuan &lt;/div&gt; 四、带图标的标签 &lt;div class=&quot;ui image label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt;zhiyuan&lt;i class=&quot;red delete icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; 五、气泡框标签 &lt;div class=&quot;ui bottom pointing red label&quot;&gt;帐户或不能为空&lt;/div&gt; &lt;div class=&quot;ui top pointing red label&quot;&gt;帐户或不能为空&lt;/div&gt; &lt;div class=&quot;ui left pointing red label&quot;&gt;帐户或不能为空&lt;/div&gt; &lt;div class=&quot;ui right pointing red label&quot;&gt;帐户或不能为空&lt;/div&gt; &lt;div class=&quot;ui bottom pointing red basic label&quot;&gt;帐户或不能为空&lt;/div&gt; &lt;div class=&quot;ui top pointing red basic label&quot;&gt;帐户或不能为空&lt;/div&gt; &lt;div class=&quot;ui left pointing red basic label&quot;&gt;帐户或不能为空&lt;/div&gt; &lt;div class=&quot;ui right pointing red basic label&quot;&gt;帐户或不能为空&lt;/div&gt; 六、角落标签 &lt;div class=&quot;ui small rounded image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;a href=&quot;&quot; class=&quot;ui left corner red label&quot;&gt; &lt;i class=&quot;heart icon&quot;&gt;&lt;/i&gt; &lt;/a&gt; &lt;/div&gt; 七、书签样式的标签 &lt;div class=&quot;ui tag label&quot;&gt;标签&lt;/div&gt; &lt;div class=&quot;ui red tag label&quot;&gt;标签&lt;/div&gt; &lt;div class=&quot;ui blue tag label&quot;&gt;标签&lt;/div&gt; &lt;div class=&quot;ui green tag label&quot;&gt;标签&lt;/div&gt; 八、绶带样式标签 &lt;div class=&quot;ui medium bordered rounded image&quot;&gt; &lt;a href=&quot;&quot; class=&quot;ui red left ribbon label&quot;&gt; &lt;i class=&quot;copyright icon&quot;&gt;&lt;/i&gt;原创作品 &lt;/a&gt; &lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt; &lt;a href=&quot;&quot; class=&quot;ui green right ribbon label&quot;&gt; &lt;i class=&quot;share icon&quot;&gt;&lt;/i&gt;转载作品 &lt;/a&gt; &lt;/div&gt; 九、附加标签样式 &lt;div class=&quot;ui raised segment&quot;&gt; &lt;p&gt;&lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt; &lt;div class=&quot;ui top attached label&quot;&gt;HTML&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;p&gt;&lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt; &lt;div class=&quot;ui bottom attached label&quot;&gt;HTML&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;p&gt;&lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt; &lt;div class=&quot;ui top left attached label&quot;&gt;HTML&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;p&gt;&lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt; &lt;div class=&quot;ui top right attached label&quot;&gt;HTML&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;p&gt;&lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt; &lt;div class=&quot;ui bottom left attached label&quot;&gt;HTML&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui raised segment&quot;&gt; &lt;p&gt;&lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt; &lt;div class=&quot;ui bottom right attached label&quot;&gt;HTML&lt;/div&gt; &lt;/div&gt; 十、浮动角标标签 &lt;div class=&quot;ui compact menu&quot;&gt; &lt;a href=&quot;&quot; class=&quot;item&quot;&gt; &lt;i class=&quot;mail icon&quot;&gt;&lt;/i&gt;邮件 &lt;div class=&quot;ui floating red label&quot;&gt;99&lt;/div&gt; &lt;/a&gt; &lt;a href=&quot;&quot; class=&quot;item&quot;&gt; &lt;i class=&quot;users icon&quot;&gt;&lt;/i&gt;好友 &lt;div class=&quot;ui floating red label&quot;&gt;99&lt;/div&gt; &lt;/a&gt; &lt;/div&gt; 十一、带图片的标签 &lt;div class=&quot;ui label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt; zhiyuan &lt;/div&gt; &lt;div class=&quot;ui red label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt; zhiyuan &lt;/div&gt; &lt;div class=&quot;ui olive label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt; zhiyuan &lt;/div&gt; &lt;div class=&quot;ui yellow label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt; zhiyuan &lt;/div&gt; &lt;div class=&quot;ui green basic label&quot;&gt; &lt;img src=&quot;./img/tx.jpg&quot; alt=&quot;&quot;&gt; zhiyuan &lt;/div&gt; 十二、圆形化标签 &lt;div class=&quot;ui circular red label&quot;&gt;1&lt;/div&gt; &lt;div class=&quot;ui circular orange label&quot;&gt;2&lt;/div&gt; &lt;div class=&quot;ui circular blue label&quot;&gt;3&lt;/div&gt; &lt;div class=&quot;ui circular violet label&quot;&gt;4&lt;/div&gt; &lt;div class=&quot;ui circular green label&quot;&gt;5&lt;/div&gt; &lt;div class=&quot;ui circular olive label&quot;&gt;6&lt;/div&gt; &lt;div class=&quot;ui circular teal label&quot;&gt;7&lt;/div&gt; &lt;div class=&quot;ui circular pink label&quot;&gt;8&lt;/div&gt; &lt;div class=&quot;ui circular black label&quot;&gt;9&lt;/div&gt; &lt;div class=&quot;ui circular purple label&quot;&gt;10&lt;/div&gt; 十三、圆点标签 &lt;div class=&quot;ui red empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui orange empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui blue empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui violet empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui green empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui olive empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui teal empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui pink empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui black empty circular label&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui purple empty circular label&quot;&gt;&lt;/div&gt; 十四、不同尺寸标签 &lt;div class=&quot;ui mini label&quot;&gt;Mini&lt;/div&gt; &lt;div class=&quot;ui tiny label&quot;&gt;Tiny&lt;/div&gt; &lt;div class=&quot;ui small label&quot;&gt;Small&lt;/div&gt; &lt;div class=&quot;ui medium label&quot;&gt;Medium&lt;/div&gt; &lt;div class=&quot;ui large label&quot;&gt;Large&lt;/div&gt; &lt;div class=&quot;ui big label&quot;&gt;Big&lt;/div&gt; &lt;div class=&quot;ui huge label&quot;&gt;Huge&lt;/div&gt; &lt;div class=&quot;ui massive label&quot;&gt;Massive&lt;/div&gt; 十五、统一尺寸标签组 &lt;div class=&quot;ui big labels&quot;&gt; &lt;div class=&quot;ui label&quot;&gt;Label&lt;/div&gt; &lt;div class=&quot;ui label&quot;&gt;Label&lt;/div&gt; &lt;div class=&quot;ui label&quot;&gt;Label&lt;/div&gt; &lt;div class=&quot;ui label&quot;&gt;Label&lt;/div&gt; &lt;div class=&quot;ui label&quot;&gt;Label&lt;/div&gt; &lt;/div&gt; 十六、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/PBrRBU_6S/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/PBrRBU_6S/"},{"title":"SemanticUI学习（5）输入框","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、标准输入框 &lt;div class=&quot;ui input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;标准输入框&quot;&gt; &lt;/div&gt; 三、聚焦输入框 &lt;div class=&quot;ui focus input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;聚焦输入框&quot;&gt; &lt;/div&gt; 四、加载图标输入框 &lt;div class=&quot;ui left icon loading input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;加载图标输入框&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui right icon loading input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;加载图标输入框&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; 五、不同状态输入框 &lt;!--禁止输入框--&gt; &lt;div class=&quot;ui disabled input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;禁止输入框&quot;&gt; &lt;/div&gt; &lt;!--错误输入框--&gt; &lt;div class=&quot;ui error input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;错误输入框&quot;&gt; &lt;/div&gt; 六、带标签输入框 &lt;div class=&quot;ui labeled input&quot;&gt; &lt;div class=&quot;ui orange label&quot;&gt;https://&lt;/div&gt; &lt;input type=&quot;text&quot; placeholder=&quot;带标签输入框&quot;&gt; &lt;/div&gt; 七、带图标输入框 &lt;div class=&quot;ui right icon input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;带图标输入框&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui left icon input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;带图标输入框&quot;&gt; &lt;i class=&quot;user add icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui right icon input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;带图标输入框&quot;&gt; &lt;i class=&quot;circular search link icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui right icon input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;带图标输入框&quot;&gt; &lt;i class=&quot;circular search inverted link icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; 八、带下拉列表输入框 &lt;div class=&quot;ui right labeled input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;带下拉列表输入框&quot;&gt; &lt;div class=&quot;ui dropdown label&quot;&gt; &lt;div class=&quot;text&quot;&gt;.com&lt;/div&gt; &lt;i class=&quot;dropdown icon&quot;&gt;&lt;/i&gt; &lt;div class=&quot;menu&quot;&gt; &lt;div class=&quot;item&quot;&gt;.com&lt;/div&gt; &lt;div class=&quot;item&quot;&gt;.cn&lt;/div&gt; &lt;div class=&quot;item&quot;&gt;.org&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 九、图标+标签输入框 &lt;div class=&quot;ui right labeled left icon input&quot;&gt; &lt;i class=&quot;tags icon&quot;&gt;&lt;/i&gt; &lt;input type=&quot;text&quot; placeholder=&quot;图标标签组合&quot;&gt; &lt;div class=&quot;ui tag green label&quot;&gt;添加标签&lt;/div&gt; &lt;/div&gt; 十、角落图标输入框 &lt;div class=&quot;ui left corner labeled input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;搜索...&quot;&gt; &lt;div class=&quot;ui left corner red label&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui right corner labeled input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;搜索...&quot;&gt; &lt;div class=&quot;ui right corner red label&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;/div&gt; 十一、带按钮的输入框 &lt;div class=&quot;ui action input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;搜索...&quot;&gt; &lt;button class=&quot;ui icon button&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/button&gt; &lt;/div&gt; 十二、透明输入框 &lt;div class=&quot;ui transparent input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;搜索...&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui transparent right icon input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;搜索...&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui transparent left icon input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;搜索&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; 十三、适应容器输入框 &lt;div class=&quot;ui fluid right icon input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;搜索&quot;&gt; &lt;i class=&quot;search icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; 十四、不同尺寸输入框 &lt;div class=&quot;ui mini input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;mini输入框&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui tiny input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;tiny输入框&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui small input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;small输入框&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui medium input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;medium输入框&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui large input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;large输入框&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui big input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;big输入框&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui huge input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;huge输入框&quot;&gt; &lt;/div&gt; &lt;div class=&quot;ui massive input&quot;&gt; &lt;input type=&quot;text&quot; placeholder=&quot;massive输入框&quot;&gt; &lt;/div&gt; 十五、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/T1juUwu3S/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/T1juUwu3S/"},{"title":"SemanticUI学习（4）图片","content":"一、引入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、不同尺寸的图片 &lt;!-- 迷你图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-mini-image&lt;/div&gt; &lt;div class=&quot;ui mini image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;!-- 较小图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-tiny-image&lt;/div&gt; &lt;div class=&quot;ui tiny image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;!-- 小图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-small-image&lt;/div&gt; &lt;div class=&quot;ui small image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot; title=&quot;小图片&quot;&gt; &lt;/div&gt; &lt;!-- 中等图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-medium-image&lt;/div&gt; &lt;div class=&quot;ui medium image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot; title=&quot;中等图片&quot;&gt; &lt;/div&gt; &lt;!-- 大图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-large-image&lt;/div&gt; &lt;div class=&quot;ui large image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;!-- 较大图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-big-image&lt;/div&gt; &lt;div class=&quot;ui big image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;!-- 巨大图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-huge-image&lt;/div&gt; &lt;div class=&quot;ui huge image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;!-- 最大图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-massive-image&lt;/div&gt; &lt;div class=&quot;ui massive image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; 三、不同状态的图片 &lt;!-- 隐藏的图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-hidden-image&lt;/div&gt; &lt;div class=&quot;ui hidden image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; &lt;!-- 禁止的图片 --&gt; &lt;div class=&quot;ui block header&quot;&gt;ui-disabled-image&lt;/div&gt; &lt;div class=&quot;ui disabled small image&quot;&gt; &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot;&gt; &lt;/div&gt; 四、有边框的图片 &lt;img src=&quot;./img/white-image.png&quot; alt=&quot;&quot; class=&quot;ui bordered small image&quot;&gt; 五、适应容器的图片 &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot; class=&quot;ui fluid image&quot;&gt; 六、圆角的图片 &lt;img src=&quot;./img/image.png&quot; alt=&quot;&quot; class=&quot;ui rounded medium image&quot;&gt; 七、圆形的图片 &lt;img src=&quot;./img/square-image.png&quot; alt=&quot;&quot; class=&quot;ui circular small image&quot;&gt; 八、位置居中的图片 &lt;img src=&quot;img/paragraph.png&quot; class=&quot;ui centered aligned image&quot;&gt; 九、同一尺寸图片组 &lt;div class=&quot;ui small images&quot;&gt; &lt;img src=&quot;img/image.png&quot; class=&quot;ui image&quot;&gt; &lt;img src=&quot;img/image.png&quot; class=&quot;ui image&quot;&gt; &lt;img src=&quot;img/image.png&quot; class=&quot;ui image&quot;&gt; &lt;img src=&quot;img/image.png&quot; class=&quot;ui image&quot;&gt; &lt;/div&gt; 十、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/6S9G53Ktp/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/6S9G53Ktp/"},{"title":"SemanticUI学习（3）标题","content":"一、导入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src = &quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、六级标题 &lt;h1 class=&quot;ui header&quot;&gt;一级标题&lt;/h1&gt; &lt;h2 class=&quot;ui header&quot;&gt;二级标题&lt;/h2&gt; &lt;h3 class=&quot;ui header&quot;&gt;三级标题&lt;/h3&gt; &lt;h4 class=&quot;ui header&quot;&gt;四级标题&lt;/h4&gt; &lt;h5 class=&quot;ui header&quot;&gt;五级标题&lt;/h5&gt; &lt;h6 class=&quot;ui header&quot;&gt;六级标题&lt;/h6&gt; 三、不同尺寸标题 &lt;div class=&quot;ui huge header&quot;&gt;巨型标题&lt;/div&gt; &lt;div class=&quot;ui large header&quot;&gt;大型标题&lt;/div&gt; &lt;div class=&quot;ui medium header&quot;&gt;中型标题&lt;/div&gt; &lt;div class=&quot;ui small header&quot;&gt;小型标题&lt;/div&gt; &lt;div class=&quot;ui tiny header&quot;&gt;超小标题&lt;/div&gt; 四、语义化标题 &lt;div class=&quot;ui header&quot;&gt;标题&lt;/div&gt; &lt;div class=&quot;sub header&quot;&gt;副标题内容&lt;/div&gt; 五、带图标标题 &lt;div class=&quot;ui header&quot;&gt; &lt;img src=&quot;./img/school.png&quot; alt=&quot;&quot; class=&quot;ui image&quot;&gt; &lt;div class=&quot;content&quot;&gt;了解更多&lt;/div&gt; &lt;/div&gt; 六、块标题 &lt;div class=&quot;ui block header&quot;&gt;BlockHeader&lt;/div&gt; 七、顶部垂直附加标题 &lt;div class=&quot;ui top attached header&quot;&gt;顶部&lt;/div&gt; 八、底部垂直附加标题 &lt;div class=&quot;ui bottom attached header&quot;&gt;底部&lt;/div&gt; 九、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/wVXYh7sFa/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/wVXYh7sFa/"},{"title":"SemanticUI学习（2）分割线","content":"一、导入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src = &quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、普通分割线 &lt;div class=&quot;ui divider&quot;&gt;普通分割线&lt;/div&gt; 三、纵向分割线 &lt;div class=&quot;ui vertical divider&quot;&gt;纵向分割线&lt;/div&gt; 四、横向分割线 &lt;div class=&quot;ui horizontal divider&quot;&gt;横向分割线&lt;/div&gt; 五、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/P7fMkbvf8/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/P7fMkbvf8/"},{"title":"SemanticUI学习（1）按钮","content":"一、导入资源文件 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.css&quot;/&gt; &lt;script src=&quot;https://code.jquery.com/jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt; &lt;script src = &quot;https://cdn.jsdelivr.net/semantic-ui/2.2.4/semantic.min.js&quot;&gt;&lt;/script&gt; 二、不同尺寸按钮 &lt;div class=&quot;ui button&quot;&gt;默认大小&lt;/div&gt; &lt;div class=&quot;ui mini button&quot;&gt;迷你按钮&lt;/div&gt; &lt;div class=&quot;ui tiny button&quot;&gt;较小按钮&lt;/div&gt; &lt;div class=&quot;ui small button&quot;&gt;小按钮&lt;/div&gt; &lt;div class=&quot;ui large button&quot;&gt;大按钮&lt;/div&gt; &lt;div class=&quot;ui big button&quot;&gt;较大按钮&lt;/div&gt; &lt;div class=&quot;ui huge button&quot;&gt;巨大按钮&lt;/div&gt; &lt;div class=&quot;ui massive button&quot;&gt;最大按钮&lt;/div&gt; 三、不同颜色的按钮，按级别分 &lt;div class=&quot;ui primary button&quot;&gt;primary&lt;/div&gt; &lt;div class=&quot;ui secondary button&quot;&gt;secondary&lt;/div&gt; &lt;div class=&quot;ui positive button&quot;&gt;positive&lt;/div&gt; &lt;div class=&quot;ui negative button&quot;&gt;negative&lt;/div&gt; &lt;hr&gt; &lt;div class=&quot;ui primary basic button&quot;&gt;primary&lt;/div&gt; &lt;div class=&quot;ui secondary basic button&quot;&gt;secondary&lt;/div&gt; &lt;div class=&quot;ui positive basic button&quot;&gt;positive&lt;/div&gt; &lt;div class=&quot;ui negative basic button&quot;&gt;negative&lt;/div&gt; &lt;hr&gt; &lt;div class=&quot;ui primary inverted button&quot;&gt;primary&lt;/div&gt; &lt;div class=&quot;ui secondary inverted button&quot;&gt;secondary&lt;/div&gt; &lt;div class=&quot;ui positive inverted button&quot;&gt;positive&lt;/div&gt; &lt;div class=&quot;ui negative inverted button&quot;&gt;negative&lt;/div&gt; 四、不同颜色的按钮 &lt;div class=&quot;ui teal button&quot;&gt;teal&lt;/div&gt; &lt;div class=&quot;ui red button&quot;&gt;red&lt;/div&gt; &lt;div class=&quot;ui orange button&quot;&gt;orange&lt;/div&gt; &lt;div class=&quot;ui yellow button&quot;&gt;yellow&lt;/div&gt; &lt;div class=&quot;ui olive button&quot;&gt;olive&lt;/div&gt; &lt;div class=&quot;ui green button&quot;&gt;green&lt;/div&gt; &lt;div class=&quot;ui blue button&quot;&gt;blue&lt;/div&gt; &lt;div class=&quot;ui violet button&quot;&gt;violet&lt;/div&gt; &lt;div class=&quot;ui purple button&quot;&gt;purple&lt;/div&gt; &lt;div class=&quot;ui pink button&quot;&gt;pink&lt;/div&gt; &lt;div class=&quot;ui brown button&quot;&gt;brown&lt;/div&gt; &lt;div class=&quot;ui grey button&quot;&gt;grey&lt;/div&gt; &lt;div class=&quot;ui black button&quot;&gt;black&lt;/div&gt; &lt;hr&gt; &lt;div class=&quot;ui teal basic button&quot;&gt;teal&lt;/div&gt; &lt;div class=&quot;ui red basic button&quot;&gt;red&lt;/div&gt; &lt;div class=&quot;ui orange basic button&quot;&gt;orange&lt;/div&gt; &lt;div class=&quot;ui yellow basic button&quot;&gt;yellow&lt;/div&gt; &lt;div class=&quot;ui olive basic button&quot;&gt;olive&lt;/div&gt; &lt;div class=&quot;ui green basic button&quot;&gt;green&lt;/div&gt; &lt;div class=&quot;ui blue basic button&quot;&gt;blue&lt;/div&gt; &lt;div class=&quot;ui violet basic button&quot;&gt;violet&lt;/div&gt; &lt;div class=&quot;ui purple basic button&quot;&gt;purple&lt;/div&gt; &lt;div class=&quot;ui pink basic button&quot;&gt;pink&lt;/div&gt; &lt;div class=&quot;ui brown basic button&quot;&gt;brown&lt;/div&gt; &lt;div class=&quot;ui grey basic button&quot;&gt;grey&lt;/div&gt; &lt;div class=&quot;ui black basic button&quot;&gt;black&lt;/div&gt; &lt;hr&gt; &lt;div class=&quot;ui teal inverted button&quot;&gt;teal&lt;/div&gt; &lt;div class=&quot;ui red inverted button&quot;&gt;red&lt;/div&gt; &lt;div class=&quot;ui orange inverted button&quot;&gt;orange&lt;/div&gt; &lt;div class=&quot;ui yellow inverted button&quot;&gt;yellow&lt;/div&gt; &lt;div class=&quot;ui olive inverted button&quot;&gt;olive&lt;/div&gt; &lt;div class=&quot;ui green inverted button&quot;&gt;green&lt;/div&gt; &lt;div class=&quot;ui blue inverted button&quot;&gt;blue&lt;/div&gt; &lt;div class=&quot;ui violet inverted button&quot;&gt;violet&lt;/div&gt; &lt;div class=&quot;ui purple inverted button&quot;&gt;purple&lt;/div&gt; &lt;div class=&quot;ui pink inverted button&quot;&gt;pink&lt;/div&gt; &lt;div class=&quot;ui brown inverted button&quot;&gt;brown&lt;/div&gt; &lt;div class=&quot;ui grey inverted button&quot;&gt;grey&lt;/div&gt; &lt;div class=&quot;ui black inverted button&quot;&gt;black&lt;/div&gt; 五、有动画效果的按钮 &lt;div class=&quot;ui animated red button&quot;&gt; &lt;div class=&quot;visible content&quot;&gt;购物&lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt; &lt;i class=&quot;shop icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- 修改为垂直动画效果 --&gt; &lt;div class=&quot;ui vertical animated green button&quot;&gt; &lt;div class=&quot;visible content&quot;&gt;回到顶部&lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt; &lt;i class=&quot;arrow up icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- 修改为渐隐动画效果 --&gt; &lt;div class=&quot;ui fade animated orange button&quot;&gt; &lt;div class=&quot;visible content&quot;&gt;只要 ￥9.9&lt;/div&gt; &lt;div class=&quot;hidden content&quot;&gt;只要 $9.9&lt;/div&gt; &lt;/div&gt; 六、带标签按钮 &lt;div class=&quot;ui labeled button&quot;&gt; &lt;div class=&quot;ui red button&quot;&gt; &lt;i class=&quot;shop icon&quot;&gt;&lt;/i&gt;Total &lt;/div&gt; &lt;div class=&quot;ui red basic label&quot;&gt;￥99999&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui right labeled button&quot;&gt; &lt;div class=&quot;ui red button&quot;&gt; &lt;i class=&quot;shop icon&quot;&gt;&lt;/i&gt;Total &lt;/div&gt; &lt;div class=&quot;ui red basic label&quot;&gt;￥99999&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui left labeled button&quot;&gt; &lt;div class=&quot;ui red basic right pointing label&quot;&gt;￥99999&lt;/div&gt; &lt;div class=&quot;ui red button&quot;&gt; &lt;i class=&quot;shop icon&quot;&gt;&lt;/i&gt;Total &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui left labeled button&quot;&gt; &lt;div class=&quot;ui red basic right pointing label&quot;&gt;￥99999&lt;/div&gt; &lt;div class=&quot;ui red button&quot;&gt; &lt;i class=&quot;shop icon&quot;&gt;&lt;/i&gt;Total &lt;/div&gt; &lt;/div&gt; 七、图标按钮 &lt;div class=&quot;ui icon red button&quot;&gt; &lt;i class=&quot;shop icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; 八、标签按钮 &lt;div class=&quot;ui left labeled icon red button&quot;&gt; &lt;i class=&quot;cart plus icon&quot;&gt;&lt;/i&gt;加入购物车 &lt;/div&gt; &lt;div class=&quot;ui right labeled icon orange button&quot;&gt; &lt;i class=&quot;trash alternate icon&quot;&gt;&lt;/i&gt;清空购物车 &lt;/div&gt; 九、普通按钮组合 &lt;div class=&quot;ui buttons&quot;&gt; &lt;div class=&quot;ui green button&quot;&gt;按钮1&lt;/div&gt; &lt;div class=&quot;ui olive button&quot;&gt;按钮2&lt;/div&gt; &lt;div class=&quot;ui violet button&quot;&gt;按钮3&lt;/div&gt; &lt;div class=&quot;ui yellow button&quot;&gt;按钮4&lt;/div&gt; &lt;div class=&quot;ui orange button&quot;&gt;按钮5&lt;/div&gt; &lt;/div&gt; 十、图标按钮组合 &lt;div class=&quot;ui icon buttons&quot;&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;align left icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;align center icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;align right icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;align justify icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;bold icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;underline icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;text width icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;eraser icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;linkify icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;edit icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;italic icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;font icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;div class=&quot;ui icon button&quot;&gt;&lt;i class=&quot;share icon&quot;&gt;&lt;/i&gt;&lt;/div&gt; &lt;/div&gt; 十一、条件选择按钮 &lt;div class=&quot;ui buttons&quot;&gt; &lt;div class=&quot;ui green button&quot;&gt;Confirm&lt;/div&gt; &lt;div class=&quot;or&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui red button&quot;&gt;Cancle&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui buttons&quot;&gt; &lt;div class=&quot;ui green button&quot;&gt;Save&lt;/div&gt; &lt;div class=&quot;or&quot; data-text=&quot;&amp;&quot;&gt;&lt;/div&gt; &lt;div class=&quot;ui red button&quot;&gt;Quit&lt;/div&gt; &lt;/div&gt; 十二、不同状态的按钮 &lt;div class=&quot;ui red button&quot;&gt;默认状态&lt;/div&gt; &lt;div class=&quot;ui red active button&quot;&gt;激活状态&lt;/div&gt; &lt;div class=&quot;ui red disabled button&quot;&gt;禁止状态&lt;/div&gt; 十三、加载按钮 &lt;div class=&quot;ui primary loading button&quot;&gt;加载按钮&lt;/div&gt; 十四、个别平台配色按钮 &lt;div class=&quot;ui facebook button&quot;&gt; &lt;i class=&quot;facebook icon&quot;&gt;&lt;/i&gt;Facebook &lt;/div&gt; &lt;div class=&quot;ui twitter button&quot;&gt; &lt;i class=&quot;twitter icon&quot;&gt;&lt;/i&gt;Facebook &lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt; &lt;i class=&quot;qq icon&quot;&gt;&lt;/i&gt;QQ &lt;/div&gt; &lt;div class=&quot;ui google button&quot;&gt; &lt;i class=&quot;google icon&quot;&gt;&lt;/i&gt;Google &lt;/div&gt; &lt;div class=&quot;ui instagram button&quot;&gt; &lt;i class=&quot;instagram icon&quot;&gt;&lt;/i&gt;instagram &lt;/div&gt; &lt;div class=&quot;ui youtube button&quot;&gt; &lt;i class=&quot;youtube icon&quot;&gt;&lt;/i&gt;Facebook &lt;/div&gt; 十五、按钮浮动 &lt;div class=&quot;ui left floated button&quot;&gt;左浮按钮&lt;/div&gt; &lt;div class=&quot;ui right floated button&quot;&gt;右浮按钮&lt;/div&gt; 十六、开关按钮 &lt;button class=&quot;ui toggle button&quot;&gt;投票&lt;/button&gt; 十七、适应容器按钮 &lt;div class=&quot;ui fluid button&quot;&gt;适应容器&lt;/div&gt; 十八、圆形按钮 &lt;div class=&quot;ui circular icon blue button&quot;&gt; &lt;i class=&quot;setting icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui circular icon red button&quot;&gt; &lt;i class=&quot;qq icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui circular icon green button&quot;&gt; &lt;i class=&quot;wechat icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui circular icon twitter button&quot;&gt; &lt;i class=&quot;twitter icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui circular icon youtube button&quot;&gt; &lt;i class=&quot;youtube icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui circular icon yellow button&quot;&gt; &lt;i class=&quot;github icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui circular icon black button&quot;&gt; &lt;i class=&quot;steam symbol icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui circular icon red button&quot;&gt; &lt;i class=&quot;weibo icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; &lt;div class=&quot;ui circular icon blue button&quot;&gt; &lt;i class=&quot;telegram plane icon&quot;&gt;&lt;/i&gt; &lt;/div&gt; 十九、垂直附加按钮 &lt;div class=&quot;container&quot; style=&quot;width:800px;margin:0 auto;&quot;&gt; &lt;div class=&quot;ui top attached blue button&quot;&gt;顶部&lt;/div&gt; &lt;div class=&quot;ui attached segment&quot;&gt; &lt;p&gt;&lt;img src=&quot;./img/paragraph.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui bottom attached blue button&quot;&gt;底部&lt;/div&gt; &lt;/div&gt; 二十、垂直附加多个按钮 &lt;div class=&quot;container&quot; style=&quot;width:800px;margin:0 auto;&quot;&gt; &lt;div class=&quot;ui top attached buttons&quot;&gt; &lt;div class=&quot;ui button&quot;&gt;左&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;右&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ui attached segment&quot;&gt; &lt;p&gt;&lt;img src=&quot;&quot; alt=&quot;&quot;&gt;&lt;/p&gt; &lt;/div&gt; &lt;div class=&quot;ui bottom attached buttons&quot;&gt; &lt;div class=&quot;ui button&quot;&gt;左&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;右&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 二十一、垂直按钮组合 &lt;div class=&quot;ui vertical labeled icon buttons&quot;&gt; &lt;div class=&quot;ui button active&quot;&gt;&lt;i class=&quot;home icon&quot;&gt;&lt;/i&gt;首页&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;&lt;i class=&quot;book icon&quot;&gt;&lt;/i&gt;文档&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;&lt;i class=&quot;tags icon&quot;&gt;&lt;/i&gt;标签&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;&lt;i class=&quot;database icon&quot;&gt;&lt;/i&gt;数据库&lt;/div&gt; &lt;/div&gt; 二十二、限定按钮个数的按钮组 &lt;div class=&quot;container&quot; style=&quot;width:800px;margin:0 auto;&quot;&gt; &lt;div class=&quot;ui five blue buttons&quot;&gt; &lt;div class=&quot;ui button&quot;&gt;按钮1&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;按钮2&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;按钮3&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;按钮4&lt;/div&gt; &lt;div class=&quot;ui button&quot;&gt;按钮5&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; 二十三、实际效果 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/zylvLeQTD/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/zylvLeQTD/"},{"title":"常见MimeType记录","content":"一、MimeType大全 文件扩展名 Content-Type(Mime-Type) 文件扩展名 Content-Type(Mime-Type) .*（ 字节流，未知下载文件类型时使用） application/octet-stream .tif image/tiff .001 application/x-001 .301 application/x-301 .323 text/h323 .906 application/x-906 .907 drawing/907 .a11 application/x-a11 .acp audio/x-mei-aac .ai application/postscript .aif audio/aiff .aifc audio/aiff .aiff audio/aiff .anv application/x-anv .asa text/asa .asf video/x-ms-asf .asp text/asp .asx video/x-ms-asf .au audio/basic .avi video/avi .awf application/vnd.adobe.workflow .biz text/xml .bmp application/x-bmp .bot application/x-bot .c4t application/x-c4t .c90 application/x-c90 .cal application/x-cals .cat application/vnd.ms-pki.seccat .cdf application/x-netcdf .cdr application/x-cdr .cel application/x-cel .cer application/x-x509-ca-cert .cg4 application/x-g4 .cgm application/x-cgm .cit application/x-cit .class java/* .cml text/xml .cmp application/x-cmp .cmx application/x-cmx .cot application/x-cot .crl application/pkix-crl .crt application/x-x509-ca-cert .csi application/x-csi .css text/css .cut application/x-cut .dbf application/x-dbf .dbm application/x-dbm .dbx application/x-dbx .dcd text/xml .dcx application/x-dcx .der application/x-x509-ca-cert .dgn application/x-dgn .dib application/x-dib .dll application/x-msdownload .doc application/msword .dot application/msword .drw application/x-drw .dtd text/xml .dwf Model/vnd.dwf .dwf application/x-dwf .dwg application/x-dwg .dxb application/x-dxb .dxf application/x-dxf .edn application/vnd.adobe.edn .emf application/x-emf .eml message/rfc822 .ent text/xml .epi application/x-epi .eps application/x-ps .eps application/postscript .etd application/x-ebx .exe application/x-msdownload .fax image/fax .fdf application/vnd.fdf .fif application/fractals .fo text/xml .frm application/x-frm .g4 application/x-g4 .gbr application/x-gbr . application/x- .gif image/gif .gl2 application/x-gl2 .gp4 application/x-gp4 .hgl application/x-hgl .hmr application/x-hmr .hpg application/x-hpgl .hpl application/x-hpl .hqx application/mac-binhex40 .hrf application/x-hrf .hta application/hta .htc text/x-component .htm text/html .html text/html .htt text/webviewhtml .htx text/html .icb application/x-icb .ico image/x-icon .ico application/x-ico .iff application/x-iff .ig4 application/x-g4 .igs application/x-igs .iii application/x-iphone .img application/x-img .ins application/x-internet-signup .isp application/x-internet-signup .IVF video/x-ivf .java java/* .jfif image/jpeg .jpe image/jpeg .jpe application/x-jpe .jpeg image/jpeg .jpg image/jpeg .jpg application/x-jpg .js application/x-javascript .jsp text/html .la1 audio/x-liquid-file .lar application/x-laplayer-reg .latex application/x-latex .lavs audio/x-liquid-secure .lbm application/x-lbm .lmsff audio/x-la-lms .ls application/x-javascript .ltr application/x-ltr .m1v video/x-mpeg .m2v video/x-mpeg .m3u audio/mpegurl .m4e video/mpeg4 .mac application/x-mac .man application/x-troff-man .math text/xml .mdb application/msaccess .mdb application/x-mdb .mfp application/x-shockwave-flash .mht message/rfc822 .mhtml message/rfc822 .mi application/x-mi .mid audio/mid .midi audio/mid .mil application/x-mil .mml text/xml .mnd audio/x-musicnet-download .mns audio/x-musicnet-stream .mocha application/x-javascript .movie video/x-sgi-movie .mp1 audio/mp1 .mp2 audio/mp2 .mp2v video/mpeg .mp3 audio/mp3 .mp4 video/mpeg4 .mpa video/x-mpg .mpd application/vnd.ms-project .mpe video/x-mpeg .mpeg video/mpg .mpg video/mpg .mpga audio/rn-mpeg .mpp application/vnd.ms-project .mps video/x-mpeg .mpt application/vnd.ms-project .mpv video/mpg .mpv2 video/mpeg .mpw application/vnd.ms-project .mpx application/vnd.ms-project .mtx text/xml .mxp application/x-mmxp .net image/pnetvue .nrf application/x-nrf .nws message/rfc822 .odc text/x-ms-odc .out application/x-out .p10 application/pkcs10 .p12 application/x-pkcs12 .p7b application/x-pkcs7-certificates .p7c application/pkcs7-mime .p7m application/pkcs7-mime .p7r application/x-pkcs7-certreqresp .p7s application/pkcs7-signature .pc5 application/x-pc5 .pci application/x-pci .pcl application/x-pcl .pcx application/x-pcx .pdf application/pdf .pdf application/pdf .pdx application/vnd.adobe.pdx .pfx application/x-pkcs12 .pgl application/x-pgl .pic application/x-pic .pko application/vnd.ms-pki.pko .pl application/x-perl .plg text/html .pls audio/scpls .plt application/x-plt .png image/png .png application/x-png .pot application/vnd.ms-powerpoint .ppa application/vnd.ms-powerpoint .ppm application/x-ppm .pps application/vnd.ms-powerpoint .ppt application/vnd.ms-powerpoint .ppt application/x-ppt .pr application/x-pr .prf application/pics-rules .prn application/x-prn .prt application/x-prt .ps application/x-ps .ps application/postscript .ptn application/x-ptn .pwz application/vnd.ms-powerpoint .r3t text/vnd.rn-realtext3d .ra audio/vnd.rn-realaudio .ram audio/x-pn-realaudio .ras application/x-ras .rat application/rat-file .rdf text/xml .rec application/vnd.rn-recording .red application/x-red .rgb application/x-rgb .rjs application/vnd.rn-realsystem-rjs .rjt application/vnd.rn-realsystem-rjt .rlc application/x-rlc .rle application/x-rle .rm application/vnd.rn-realmedia .rmf application/vnd.adobe.rmf .rmi audio/mid .rmj application/vnd.rn-realsystem-rmj .rmm audio/x-pn-realaudio .rmp application/vnd.rn-rn_music_package .rms application/vnd.rn-realmedia-secure .rmvb application/vnd.rn-realmedia-vbr .rmx application/vnd.rn-realsystem-rmx .rnx application/vnd.rn-realplayer .rp image/vnd.rn-realpix .rpm audio/x-pn-realaudio-plugin .rsml application/vnd.rn-rsml .rt text/vnd.rn-realtext .rtf application/msword .rtf application/x-rtf .rv video/vnd.rn-realvideo .sam application/x-sam .sat application/x-sat .sdp application/sdp .sdw application/x-sdw .sit application/x-stuffit .slb application/x-slb .sld application/x-sld .slk drawing/x-slk .smi application/smil .smil application/smil .smk application/x-smk .snd audio/basic .sol text/plain .sor text/plain .spc application/x-pkcs7-certificates .spl application/futuresplash .spp text/xml .ssm application/streamingmedia .sst application/vnd.ms-pki.certstore .stl application/vnd.ms-pki.stl .stm text/html .sty application/x-sty .svg text/xml .swf application/x-shockwave-flash .tdf application/x-tdf .tg4 application/x-tg4 .tga application/x-tga .tif image/tiff .tif application/x-tif .tiff image/tiff .tld text/xml .top drawing/x-top .torrent application/x-bittorrent .tsd text/xml .txt text/plain .uin application/x-icq .uls text/iuls .vcf text/x-vcard .vda application/x-vda .vdx application/vnd.visio .vml text/xml .vpg application/x-vpeg005 .vsd application/vnd.visio .vsd application/x-vsd .vss application/vnd.visio .vst application/vnd.visio .vst application/x-vst .vsw application/vnd.visio .vsx application/vnd.visio .vtx application/vnd.visio .vxml text/xml .wav audio/wav .wax audio/x-ms-wax .wb1 application/x-wb1 .wb2 application/x-wb2 .wb3 application/x-wb3 .wbmp image/vnd.wap.wbmp .wiz application/msword .wk3 application/x-wk3 .wk4 application/x-wk4 .wkq application/x-wkq .wks application/x-wks .wm video/x-ms-wm .wma audio/x-ms-wma .wmd application/x-ms-wmd .wmf application/x-wmf .wml text/vnd.wap.wml .wmv video/x-ms-wmv .wmx video/x-ms-wmx .wmz application/x-ms-wmz .wp6 application/x-wp6 .wpd application/x-wpd .wpg application/x-wpg .wpl application/vnd.ms-wpl .wq1 application/x-wq1 .wr1 application/x-wr1 .wri application/x-wri .wrk application/x-wrk .ws application/x-ws .ws2 application/x-ws .wsc text/scriptlet .wsdl text/xml .wvx video/x-ms-wvx .xdp application/vnd.adobe.xdp .xdr text/xml .xfd application/vnd.adobe.xfd .xfdf application/vnd.adobe.xfdf .xhtml text/html .xls application/vnd.ms-excel .xls application/x-xls .xlw application/x-xlw .xml text/xml .xpl audio/scpls .xq text/xml .xql text/xml .xquery text/xml .xsd text/xml .xsl text/xml .xslt text/xml .xwd application/x-xwd .x_b application/x-x_b .sis application/vnd.symbian.install .sisx application/vnd.symbian.install .x_t application/x-x_t .ipa application/vnd.iphone .apk application/vnd.android.package-archive .xap application/x-silverlight-app 二、声明 菜鸟教程链接：https://www.runoob.com/http/http-content-type.html 本表格为转载菜鸟教程表格内容，非本人收集，转载请注明原文链接 ","link":"https://zydnc.gitee.io/3mpH-6w7/"},{"title":"Graphics绘制普通验证码","content":"一、创建ServletDemo继承HttpServlet，实现doGet和doPost方法 @WebServlet(urlPatterns = &quot;/checkCodeServlet&quot;) public class CheckServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int width = 100; int height = 30; String checkCodes = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&quot;; int codeLen = 4; int lineCount = 20; int pointCount = 100; int spacing = width/codeLen; Random ran = new Random(); StringBuilder sb = new StringBuilder(); //创建画布 BufferedImage bufferedImage = new BufferedImage(width,height, BufferedImage.TYPE_INT_RGB); //创建画笔 Graphics graphics = bufferedImage.createGraphics(); //设置画笔颜色 graphics.setColor(new Color(238,238,238)); //填充背景 graphics.fillRect(0,0,width,height); //设置字体 Font font = new Font(&quot;微软雅黑&quot;,Font.BOLD,20); if(font!=null){ graphics.setFont(font); } //重设颜色 graphics.setColor(new Color(162,162,162)); //绘制干扰点 for (int i = 0; i &lt; pointCount; i++) { int x = MyUtils.getRanNum(0,width); int y = MyUtils.getRanNum(0,height); graphics.drawLine(x,y,x+1,y+1); } //重设颜色 graphics.setColor(new Color(38,190,133)); //绘制验证码 for (int i = 0; i &lt; codeLen; i++) { String code = String.valueOf(checkCodes.charAt(MyUtils.getRanNum(0,checkCodes.length()))); int x = MyUtils.getRanNum(i*spacing,Math.abs((i+1)*spacing-font.getSize()/2)); int y = MyUtils.getRanNum(font.getSize(),height); graphics.drawString(code, x,y); sb.append(code); System.out.println(&quot;x:&quot;+x+&quot;\\ty:&quot;+y); } //重设颜色 graphics.setColor(new Color(148,218,248)); //绘制干扰线 for (int i = 0; i &lt; lineCount; i++) { int x1 = MyUtils.getRanNum(0,width); int y1 = MyUtils.getRanNum(0,height); int x2 = MyUtils.getRanNum(x1,width); int y2 = MyUtils.getRanNum(y1,height); graphics.drawLine(x1,y1,x2,y2); } System.out.println(sb); request.getSession().setAttribute(&quot;checkCodeSession&quot;,sb.toString()); ImageIO.write(bufferedImage,&quot;png&quot;,response.getOutputStream()); } } 二、实际效果如下 这种验证码还是比较古老了，早已经不再安全了，写这篇文章就是为了记录一下验证码的简单绘制，不适合用于项目中的验证 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/zEKFZIKMH/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/zEKFZIKMH/"},{"title":"正则表达式模拟实现C3P0读取配置文件创建Connection对象","content":" 本教程仅限于 mysql-connectorv8.0.21 + mysql8.0 + jdk1.8 一、在pom.xml中加入JDBC驱动JAR包的依赖项 &lt;!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --&gt; &lt;dependency&gt; &lt;groupId&gt;mysql&lt;/groupId&gt; &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt; &lt;version&gt;8.0.21&lt;/version&gt; &lt;/dependency&gt; 二、书写配置文件，并置于resources目录下 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;c3p0-config&gt; &lt;default-config&gt; &lt;property name=&quot;driverClass&quot;&gt;com.mysql.cj.jdbc.Driver&lt;/property&gt; &lt;property name=&quot;jdbcUrl&quot;&gt;jdbc:mysql://localhost:3306/test?useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;serverTimezone=UTC&lt;/property&gt; &lt;property name=&quot;user&quot;&gt;root&lt;/property&gt; &lt;property name=&quot;password&quot;&gt;123456&lt;/property&gt; &lt;/default-config&gt; &lt;/c3p0-config&gt; 三、书写JDBC简单工具类 public class JDBCUtils { private JDBCUtils(){} public static Connection getConnection(String driverClass,String jdbcUrl,String user,String password){ Connection conn = null; try { Class.forName(driverClass); conn = DriverManager.getConnection(jdbcUrl,user,password); } catch (SQLException | ClassNotFoundException e) { e.printStackTrace(); } return conn; } } 四、书写代码，是用正则表达式实现xml配置文件解析 public class RegexDemo { public static void main(String[] args) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(RegexDemo.class.getClassLoader().getResourceAsStream(&quot;c3p0-config.xml&quot;), &quot;UTF-8&quot;)); String str; while((str=br.readLine())!=null){ sb.append(str); } Pattern p = compile(&quot;&lt;property name=\\&quot;(.*?)\\&quot;&gt;(.*?)&lt;/property&gt;&quot;); Matcher matcher = p.matcher(sb); HashMap&lt;String, String&gt; config = new HashMap&lt;String, String&gt;(); while(matcher.find()){ config.put(matcher.group(1),matcher.group(2)); } Set&lt;Map.Entry&lt;String, String&gt;&gt; sets = config.entrySet(); for (Map.Entry&lt;String, String&gt; set : sets) { System.out.println(set.getKey()+&quot;:&quot;+set.getValue()); } String driverClass = config.get(&quot;driverClass&quot;); String jdbcUrl = URLDecoder.decode(config.get(&quot;jdbcUrl&quot;)); jdbcUrl = jdbcUrl.replaceAll(&quot;&amp;amp;&quot;,&quot;&amp;&quot;); String user = config.get(&quot;user&quot;); String password = config.get(&quot;password&quot;); Connection conn = JDBCUtils.getConnection(driverClass, jdbcUrl, user, password); System.out.println(conn); } } 五、成功获取所需信息，输出结果如下 password:123456 driverClass:com.mysql.cj.jdbc.Driver jdbcUrl:jdbc:mysql://localhost:3306/test?useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;serverTimezone=UTC user:root com.mysql.cj.jdbc.ConnectionImpl@49070868 注意：在第四步的时候，我做了一个额外操作，将jdbcUrl中的&amp;amp;转义字符全部转换为&amp;，否则url是错误的，是不上数据库的！ 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/UBe69VI0p/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/UBe69VI0p/"},{"title":"使用Jsoup解析XML文档","content":" 本教程仅限于 jsoupv1.11.3 + jsoupXPathv0.3.2 + jdk1.8 一、在pom.xml中添加jsoup和jsoupXPath依赖 &lt;!-- https://mvnrepository.com/artifact/org.jsoup/jsoup --&gt; &lt;dependency&gt; &lt;groupId&gt;org.jsoup&lt;/groupId&gt; &lt;artifactId&gt;jsoup&lt;/artifactId&gt; &lt;version&gt;1.11.3&lt;/version&gt; &lt;/dependency&gt; &lt;!-- https://mvnrepository.com/artifact/cn.wanghaomiao/JsoupXpath --&gt; &lt;dependency&gt; &lt;groupId&gt;cn.wanghaomiao&lt;/groupId&gt; &lt;artifactId&gt;JsoupXpath&lt;/artifactId&gt; &lt;version&gt;0.3.2&lt;/version&gt; &lt;/dependency&gt; 二、Jsoup概述 概述：Jsoup是一个专门用于解析HTML文档的技术，但是它太好用了，所以也被用来解析同为标记语言的xml文档 常用对象：Jsoup Document Elements Element Node 1. Jsoup对象 parse(File in,String charsetName)：读取文档，创建Document对象 parse(String html)：解析文档字符串创建Document对象 parse(URL url,int timeoutMillis)：通过URL读取网页源码创建Document对象 2. Document对象 getElementsByTag(String tagName)：根据标签名称获取标签元素集合Elements对象 getElementsByAttribute(String key)：根据指定属性获取含有该属性的标签元素集合Elements对象 getElementsByAttributeValue(String key,String value)：根据指定属性和属性值获取含有该属性对的标签元素集合Elements对象 getElementsById(String id)：根据指定id值获取含有该id属性值的标签元素集合Elements对象 3. Elements对象 继承自ArrayList&lt;Element&gt;，用于存放标签元素的集合，同ArrayList使用方式一样 4. Element对象 获取子元素的方法同Document getElementsByTag(String tagName)：根据标签名称获取标签元素集合Elements对象 getElementsByAttribute(String key)：根据指定属性获取含有该属性的标签元素集合Elements对象 getElementsByAttributeValue(String key,String value)：根据指定属性和属性值获取含有该属性对的标签元素集合Elements对象 getElementsById(String id)：根据指定id值获取含有该id属性值的标签元素集合Elements对象 获取属性值的方法 String attr(String key)：根据属性名称获取属性值 获取文本内容 String text()：获取文本内容 String html()：获取标签内所有内容 三、书写代码，实现解析 c3p0-config.xml 目标：解析xml文件，获得 driverClass ，jdbcUrl， user ，password &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt; &lt;c3p0-config&gt; &lt;!--使用默认的配置读取连接池对象--&gt; &lt;default-config&gt; &lt;!-- 连接参数 --&gt; &lt;property name=&quot;driverClass&quot;&gt;com.mysql.cj.jdbc.Driver&lt;/property&gt; &lt;property name=&quot;jdbcUrl&quot;&gt;jdbc:mysql://localhost:3306/test?useUnicode=ture&amp;amp;characterEncoding=utf8&amp;amp;useSSL=false&amp;amp;serverTimezone=Hongkong&lt;/property&gt; &lt;property name=&quot;user&quot;&gt;root&lt;/property&gt; &lt;property name=&quot;password&quot;&gt;123456&lt;/property&gt; &lt;!-- 初始化连接数 --&gt; &lt;property name=&quot;initialPoolSize&quot;&gt;10&lt;/property&gt; &lt;!-- 最大空闲时间 --&gt; &lt;property name=&quot;maxIdleTime&quot;&gt;30&lt;/property&gt; &lt;!-- 最大连接数 --&gt; &lt;property name=&quot;maxPoolSize&quot;&gt;100&lt;/property&gt; &lt;!-- 最小连接数 --&gt; &lt;property name=&quot;minPoolSize&quot;&gt;10&lt;/property&gt; &lt;!-- 超时时间 --&gt; &lt;property name=&quot;checkoutTimeout&quot;&gt;3000&lt;/property&gt; &lt;property name=&quot;acquireRetryAttempts&quot;&gt;30&lt;/property&gt; &lt;property name=&quot;maxStatements&quot;&gt;0&lt;/property&gt; &lt;property name=&quot;maxStatementsPerConnection&quot;&gt;0&lt;/property&gt; &lt;/default-config&gt; &lt;/c3p0-config&gt; 1. getElementsByTag(String tagName)方法 public class JsoupDemo { public static void main(String[] args) throws IOException { String path = URLDecoder.decode(JsoupDemo.class.getClassLoader().getResource(&quot;c3p0-config.xml&quot;).getPath()); Document document = Jsoup.parse(new File(path), &quot;utf-8&quot;); Elements propertys = document.getElementsByTag(&quot;property&quot;); for (Element property : propertys) { System.out.println(property.text()); } } } //控制台打印结果 com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/test?useUnicode=ture&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Hongkong root 123456 10 30 100 10 3000 30 0 0 2. getElementsByAttribute(String key)方法 public class JsoupDemo { public static void main(String[] args) throws IOException { String path = URLDecoder.decode(JsoupDemo.class.getClassLoader().getResource(&quot;c3p0-config.xml&quot;).getPath()); Document document = Jsoup.parse(new File(path), &quot;utf-8&quot;); Elements names = document.getElementsByAttribute(&quot;name&quot;); for (Element name : names) { System.out.println(name.text()); } } } //控制台打印结果 com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/test?useUnicode=ture&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Hongkong root 123456 10 30 100 10 3000 30 0 0 3. getElementsByAttributeValue(String key,String value)方法 public class JsoupDemo { public static void main(String[] args) throws IOException { String path = URLDecoder.decode(JsoupDemo.class.getClassLoader().getResource(&quot;c3p0-config.xml&quot;).getPath()); Document document = Jsoup.parse(new File(path), &quot;utf-8&quot;); Elements driverClass = document.getElementsByAttributeValue(&quot;name&quot;, &quot;driverClass&quot;); for (Element aClass : driverClass) { System.out.println(aClass.text()); } Elements jdbcUrl = document.getElementsByAttributeValue(&quot;name&quot;, &quot;jdbcUrl&quot;); for (Element element : jdbcUrl) { System.out.println(element.text()); } Elements user = document.getElementsByAttributeValue(&quot;name&quot;, &quot;user&quot;); for (Element element : user) { System.out.println(element.text()); } Elements password = document.getElementsByAttributeValue(&quot;name&quot;, &quot;password&quot;); for (Element element : password) { System.out.println(element.text()); } } } //控制台打印结果 com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/test?useUnicode=ture&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Hongkong root 123456 4. getElementsById(String id)方法 public class JsoupDemo { public static void main(String[] args) throws IOException { String path = URLDecoder.decode(JsoupDemo.class.getClassLoader().getResource(&quot;c3p0-config.xml&quot;).getPath(), &quot;UTF-8&quot;); Document document = Jsoup.parse(new File(path), &quot;UTF-8&quot;); Element idname = document.getElementById(&quot;idname&quot;); System.out.println(idname); } } //控制台打印结果 null //因为c3p0-config.xml文件中并没有含有id的标签 四、书写代码，使用Jsoup的扩展功能解析xml文件 1. 第一步：读取xml文件获取Document对象 String url = URLDecoder.decode(JsoupDemo.class.getClassLoader().getResource(&quot;c3p0-config.xml&quot;).getPath(),&quot;UTF-8&quot;); Document parse = Jsoup.parse(new File(url), &quot;UTF-8&quot;); 2. 第二步：使用Document对象的select方法 概述：select方法支持所有基本的CSS选择器语法，我们可以通过传入指定选择器来获取指定Element对象 //标签选择器 Elements es = parse.select(&quot;property&quot;); //属性选择器 Elements es = parse.select(&quot;property[name=jdbcUrl]&quot;); //组合选择器 Elements es = parse.select(&quot;default-config &gt; property[name=jdbcUrl]&quot;); //ID选择器 Element e = parse.select(&quot;#id&quot;); //Class选择器 Elements es = parse.select(&quot;.class&quot;); 其他选择器就不演示了，大家自己研究 3. 第三步：完整代码 public class JsoupDemo { public static void main(String[] args) throws IOException { String url = URLDecoder.decode(JsoupDemo.class.getClassLoader().getResource(&quot;c3p0-config.xml&quot;).getPath(),&quot;UTF-8&quot;); Document parse = Jsoup.parse(new File(url), &quot;UTF-8&quot;); Elements e1 = parse.select(&quot;property[name=driverClass]&quot;); for (Element element : e1) { System.out.println(element.text()); } Elements e2 = parse.select(&quot;property[name=jdbcUrl]&quot;); for (Element element : e2) { System.out.println(element.text()); } Elements e3 = parse.select(&quot;property[name=user]&quot;); for (Element element : e3) { System.out.println(element.text()); } Elements e4 = parse.select(&quot;property[name=password]&quot;); for (Element element : e4) { System.out.println(element.text()); } Elements e5 = parse.select(&quot;default-config &gt; property[name=driverClass]&quot;); for (Element element : e5) { System.out.println(element.text()); } Elements e6 = parse.select(&quot;default-config &gt; property[name=jdbcUrl]&quot;); for (Element element : e6) { System.out.println(element.text()); } Elements e7 = parse.select(&quot;default-config &gt; property[name=user]&quot;); for (Element element : e7) { System.out.println(element.text()); } Elements e8 = parse.select(&quot;default-config &gt; property[name=password]&quot;); for (Element element : e8) { System.out.println(element.text()); } } } //控制台输出结果 com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/test?useUnicode=ture&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Hongkong root 123456 com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/test?useUnicode=ture&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Hongkong root 123456 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/rSoFG8-KT/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/rSoFG8-KT/"},{"title":"使用Gson创建/解析json格式文件","content":" 本教程仅限于 gsonv2.8.5 + jdk1.8 一、在pom.xml中加入gson的依赖文件 &lt;!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --&gt; &lt;dependency&gt; &lt;groupId&gt;com.google.code.gson&lt;/groupId&gt; &lt;artifactId&gt;gson&lt;/artifactId&gt; &lt;version&gt;2.8.5&lt;/version&gt; &lt;/dependency&gt; 二、书写代码，使用无参构造创建Json格式文本 此处的Person同上次的org.json一样，是一个JavaBean对象 public static String createJsonText(){ Person person = new Person(&quot;絷缘&quot;, 23, &quot;male&quot;, false, false, new String[]{&quot;Java&quot;, &quot;Linux&quot;, &quot;JavaScript&quot;, &quot;Vue&quot;, &quot;SemanticUI&quot;, &quot;NodeJS&quot;}); Gson gson = new Gson(); return gson.toJson(person); } 三、书写代码，使用GsonBuilder在创建Json之前设置高级属性 public static String createJsonText(){ Person person = new Person(&quot;絷缘&quot;, 23, &quot;male&quot;, false, false, new String[]{&quot;Java&quot;, &quot;Linux&quot;, &quot;JavaScript&quot;, &quot;Vue&quot;, &quot;SemanticUI&quot;, &quot;NodeJS&quot;}); GsonBuilder gson = new GsonBuilder(); //设置Json美化显示 gson.setPrettyPrinting(); //设置Json中key的首字母全部大写 gson.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE); Gson gs = gson.create(); return gs.toJson(person); } 四、书写代码，使用GsonBuilder在创建Json之前拥有格式化日期时间的功能 public static String createJsonText(){ PersonWithBir person = new PersonWithBir(&quot;絷缘&quot;, 23, &quot;male&quot;, false, false, new String[]{&quot;Java&quot;, &quot;Linux&quot;, &quot;JavaScript&quot;, &quot;Vue&quot;, &quot;SemanticUI&quot;, &quot;NodeJS&quot;},new Date(System.currentTimeMillis())); Gson gson = new GsonBuilder().setDateFormat(&quot;yyyy-MM-dd&quot;).setPrettyPrinting().create(); return gson.toJson(person); } 五、书写代码，实现解析Json格式文本 person.json需要自己创建置于src目录（普通项目）或resources目录下（Maven项目） public static void parseJson() throws IOException { InputStream is = GsonDemo1.class.getClassLoader().getResourceAsStream(&quot;person.json&quot;); BufferedReader br = new BufferedReader(new InputStreamReader(is, &quot;UTF-8&quot;)); Gson gson = new Gson(); Person person = gson.fromJson(br, Person.class); System.out.println(person); } 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/XXV7UsHKh/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/XXV7UsHKh/"},{"title":"使用org.json创建/解析json格式文件","content":" 本教程仅限于 org.jsonv20201115 + jdk1.8 一、在pom.xml中加入org.json的依赖文件 &lt;!-- https://mvnrepository.com/artifact/org.json/json --&gt; &lt;dependency&gt; &lt;groupId&gt;org.json&lt;/groupId&gt; &lt;artifactId&gt;json&lt;/artifactId&gt; &lt;version&gt;20201115&lt;/version&gt; &lt;/dependency&gt; 二、书写代码，使用无参构造创建Json格式文本 public static String createJsonText(){ JSONObject jsonObject = new JSONObject(); jsonObject.put(&quot;name&quot;,&quot;絷缘&quot;); jsonObject.put(&quot;age&quot;,23); jsonObject.put(&quot;gender&quot;,&quot;male&quot;); jsonObject.put(&quot;now_time&quot;,new SimpleDateFormat(&quot;yyyy-MM-dd kk:mm:ss&quot;).format(new Date(System.currentTimeMillis()))); jsonObject.put(&quot;skills&quot;,new String[]{&quot;Java&quot;,&quot;Linux&quot;,&quot;JavaScript&quot;,&quot;Vue&quot;,&quot;SemanticUI&quot;,&quot;NodeJS&quot;}); jsonObject.put(&quot;has_house&quot;,false); jsonObject.put(&quot;has_car&quot;,false); return jsonObject.toString(); } 三、书写代码，使用有参构造传入HashMap对象创建Json格式文本 public static String createJsonByMap(){ HashMap&lt;String, Object&gt; hashMap = new HashMap&lt;String, Object&gt;(); hashMap.put(&quot;name&quot;,&quot;絷缘&quot;); hashMap.put(&quot;age&quot;,23); hashMap.put(&quot;gender&quot;,&quot;male&quot;); hashMap.put(&quot;now_time&quot;,new SimpleDateFormat(&quot;yyyy-MM-dd kk:mm:ss&quot;).format(new Date(System.currentTimeMillis()))); hashMap.put(&quot;skills&quot;,new String[]{&quot;Java&quot;,&quot;Linux&quot;,&quot;JavaScript&quot;,&quot;Vue&quot;,&quot;SemanticUI&quot;,&quot;NodeJS&quot;}); hashMap.put(&quot;has_house&quot;,false); hashMap.put(&quot;has_car&quot;,false); JSONObject jsonObject = new JSONObject(hashMap); return jsonObject.toString(); } 四、书写代码，封装JavaBean实现创建Json格式文本 import java.util.Arrays; public class Person { private String name; private int age; private String gender; private boolean has_house; private boolean has_car; private String [] skills; public Person(){} public Person(String name, int age, String gender, boolean has_house, boolean has_car, String[] skills) { this.name = name; this.age = age; this.gender = gender; this.has_house = has_house; this.has_car = has_car; this.skills = skills; } public String getName() { return name; } public int getAge() { return age; } public String getGender() { return gender; } public boolean isHas_house() { return has_house; } public boolean isHas_car() { return has_car; } public String[] getSkills() { return skills; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setGender(String gender) { this.gender = gender; } public void setHas_house(boolean has_house) { this.has_house = has_house; } public void setHas_car(boolean has_car) { this.has_car = has_car; } public void setSkills(String[] skills) { this.skills = skills; } } public static String createJsonByBean(){ Person person = new Person(&quot;絷缘&quot;, 23, &quot;male&quot;, false, false, new String[]{&quot;Java&quot;, &quot;Linux&quot;, &quot;JavaScript&quot;, &quot;Vue&quot;, &quot;SemanticUI&quot;, &quot;NodeJS&quot;}); JSONObject jsonObject = new JSONObject(person); return jsonObject.toString(); } 书写代码，实现解析Json文本 需要自己创建文件person.json置于src目录（普通项目）或resources目录下（Maven项目） { &quot;skills&quot;: [ &quot;Java&quot;, &quot;Linux&quot;, &quot;JavaScript&quot;, &quot;Vue&quot;, &quot;SemanticUI&quot;, &quot;NodeJS&quot; ], &quot;has_car&quot;: false, &quot;gender&quot;: &quot;male&quot;, &quot;name&quot;: &quot;絷缘&quot;, &quot;has_house&quot;: false, &quot;age&quot;: 23 } 为了方便看代码，文章中出现的异常我都会抛出 public static void parseJson() throws IOException { InputStream is = JsonDemo1.class.getClassLoader().getResourceAsStream(&quot;person.json&quot;); BufferedReader br = new BufferedReader(new InputStreamReader(is, &quot;UTF-8&quot;)); StringBuilder sb = new StringBuilder(); String content; while((content=br.readLine())!=null){ sb.append(content); } System.out.println(sb.toString()); JSONObject jsonObject = new JSONObject(sb.toString()); System.out.println(jsonObject.getString(&quot;name&quot;)); System.out.println(jsonObject.getJSONArray(&quot;skills&quot;)); System.out.println(jsonObject.getString(&quot;gender&quot;)); System.out.println(jsonObject.getInt(&quot;age&quot;)); System.out.println(jsonObject.getBoolean(&quot;has_house&quot;)); System.out.println(jsonObject.getBoolean(&quot;has_car&quot;)); } 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/4riCXnG78/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/4riCXnG78/"},{"title":"Druid数据库连接池技术","content":" 本教程仅限于 druidv1.2.4 + mysql8.0 + jdk1.8 一、在pom.xml中加入druid依赖文件 &lt;!-- https://mvnrepository.com/artifact/com.alibaba/druid --&gt; &lt;dependency&gt; &lt;groupId&gt;com.alibaba&lt;/groupId&gt; &lt;artifactId&gt;druid&lt;/artifactId&gt; &lt;version&gt;1.2.4&lt;/version&gt; &lt;/dependency&gt; 二、书写配置文件 1. druid数据库连接池技术对配置文件的命名没有要求，因为它需要我们自己拿到配置文件的IO流，然后将IO流作为参数传递，创建数据库连接池对象 2. 我就以 druid.properties 配置文件为例，继续讲解 driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/数据库名?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT username=root password=123456 #初始化连接数 initialSize=5 #最大连接数 maxActive=10 #超时时间 maxWait=3000 三、书写代码，测试正常的数据库连接对象的获取 为了方便看代码，我不去捕获异常，直接将异常抛出 public class DruidDemo{ public static void main(String[] args) throws Exception{ Properties pros = new Properties(); pros.load(new BufferedReader(new InputStreamReader(DruidDemo.class.getClassLoader().getResourceAsStream(&quot;druid.properties&quot;),&quot;UTF-8&quot;))); DataSource ds = DruidDataSourceFactory.createDataSource(pros); Connection conn = ds.getConnection(); System.out.println(conn); } } 四、进行一些CRUD操作 public class DruidDemo{ public static void main(String[] args){ Properties pros = new Properties(); pros.load(new BufferedReader(new InputStreamReader(DruidDemo.class.getClassLoader().getResourceAsStream(&quot;druid.properties&quot;),&quot;UTF-8&quot;))); DataSource ds = DruidDataSourceFactory.createDataSource(pros); Connection conn = ds.getConnection(); String sql = &quot;select * from user&quot;; PreparedStatement pstat = conn.prepareStatement(sql); ResultSet rs = pstat.executeQuery(); while(rs.next()){ System.out.println(rs.getInt(&quot;id&quot;)+&quot;\\t&quot;+rs.getString(&quot;name&quot;)+&quot;\\t&quot;+rs.getInt(&quot;age&quot;)+&quot;\\t&quot;+rs.getString(&quot;gender&quot;)+&quot;\\t&quot;); } } } 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/-gGSoZP8v/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/-gGSoZP8v/"},{"title":"C3P0数据库连接池技术","content":" 本教程仅限于 c3p0v0.9.5.2 + mysql8.0 + jdk1.8 一、在pom.xml中加入C3P0依赖文件 如果是普通项目，需要解决依赖问题，c3p0的jar包依赖于mchange-common的jar包 &lt;!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --&gt; &lt;dependency&gt; &lt;groupId&gt;com.mchange&lt;/groupId&gt; &lt;artifactId&gt;c3p0&lt;/artifactId&gt; &lt;version&gt;0.9.5.2&lt;/version&gt; &lt;/dependency&gt; 二、书写配置文件 1. 严格按照规定命名为 c3p0.properties 文件 这里需要注意和通常的不一样的是需要在每个key前加c3p0 c3p0.driverClass=com.mysql.cj.jdbc.Driver c3p0.jdbcUrl=jdbc:mysql://localhost:3306/数据库名?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimeZone=UTC c3p0.user=root c3p0.password=123456 c3p0.initialPoolSize=10 c3p0.maxPoolSize=100 c3p0.checkoutTimeout=3000 2. 严格按照规定命名为 c3p0-config.xml 文件 这里需要注意一个地方就是jdbcUrl里本来用来连接各个参数的&amp;符号在xml文件中会被转义就如同html规则一般，所以我们想要使用表示&amp;符号的转义字符&amp;amp;来替换&amp;符号 &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;&gt; &lt;c3p0-config&gt; &lt;default-config&gt; &lt;property name=&quot;driverClass&quot;&gt;&lt;/property&gt; &lt;property name=&quot;jdbcUrl&quot;&gt;jdbc:mysql://localhost:3306/数据库名?useUnicode=true&amp;amp;characterEncoding=utf8&amp;amp;serverTimeZone=UTC&lt;/property&gt; &lt;property name=&quot;user&quot;&gt;root&lt;/property&gt; &lt;property name=&quot;password&quot;&gt;123456&lt;/property&gt; &lt;property name=&quot;initialPoolSize&quot;&gt;10&lt;/property&gt; &lt;property name=&quot;maxPoolSize&quot;&gt;100&lt;/property&gt; &lt;property name=&quot;checkoutTimeout&quot;&gt;3000&lt;/property&gt; &lt;/default-config&gt; &lt;/c3p0-config&gt; 本教程以Idea工具为例，所以如果是普通Java项目，请将配置文件置于src目录下，若是Maven管理的项目，请直接将配置文件置于resources目录下，具体原因详见JDBC篇 三、书写代码，测试正常的数据连接对象的获取 为了方便看代码，我不去捕获，直接将SQL异常抛出。 public class C3P0Demo{ public static void main(String[] args){ DataSource ds = new ComboPooledDataSource(); Connection conn = null; for(int i = 0;i &lt; 20;i++){ System.out.println(ds.getConnection()); } } } 四、进行一些CRUD操作 public class C3P0Demo{ public static void main(String[] args){ DataSource ds = new ComboPooledDataSource(); Connection conn = ds.getConnection(); String sql = &quot;select * from user&quot;; PreparedStatement pstat = conn.prepareStatement(); ResultSet rs = pstat.executeQuery(); while(rs.next()){ System.out.println(rs.getInt(&quot;id&quot;)+&quot;\\t&quot;+rs.getString(&quot;name&quot;)+&quot;\\t&quot;+rs.getInt(&quot;age&quot;)+&quot;\\t&quot;+rs.getString(&quot;gender&quot;)+&quot;\\t&quot;); } } } 原文作者：絷缘 作者邮箱：zhiyuanworkemail@163.com 原文地址：https://zhiyuandnc.github.io/c3p0/ 版权声明：本文为博主原创文章，转载请注明原文链接作者信息 ","link":"https://zydnc.gitee.io/c3p0/"}]}